59d93b1468634648560c4a622e0e541cee8fccd9
[samba.git] / bootstrap / config.py
1 #!/usr/bin/env python3
2
3 # Copyright (C) Catalyst.Net Ltd 2019
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 """
19 Manage dependencies and bootstrap environments for Samba.
20
21 Config file for packages and templates.
22
23 Update the lists in this file to require new packages in the
24 container images used in GitLab CI
25
26 Author: Joe Guo <joeg@catalyst.net.nz>
27 """
28 import os
29 from os.path import abspath, dirname, join
30 HERE = abspath(dirname(__file__))
31 # output dir for rendered files
32 OUT = join(HERE, 'generated-dists')
33
34
35 # pkgs with same name in all packaging systems
36 COMMON = [
37     'acl',
38     'attr',
39     'autoconf',
40     'binutils',
41     'bison',
42     'ccache',
43     'curl',
44     'chrpath',
45     'flex',
46     'gcc',
47     'gdb',
48     'git',
49     'gzip',
50     'hostname',
51     'htop',
52     'lcov',
53     'make',
54     'patch',
55     'perl',
56     'psmisc',  # for pstree in test
57     'rng-tools',
58     'rsync',
59     'sed',
60     'sudo',  # docker images has no sudo by default
61     'tar',
62     'tree',
63     'wget',
64 ]
65
66
67 # define pkgs for all packaging systems in parallel
68 # make it easier to find missing ones
69 # use latest ubuntu and fedora as defaults
70 # deb, rpm, ...
71 PKGS = [
72     # NAME1-dev, NAME2-devel
73     ('lmdb-utils', 'lmdb'),
74     ('mingw-w64', 'mingw64-gcc'),
75     ('zlib1g-dev', 'zlib-devel'),
76     ('libbsd-dev', 'libbsd-devel'),
77     ('liburing-dev', 'liburing-devel'),
78     ('libarchive-dev', 'libarchive-devel'),
79     ('libblkid-dev', 'libblkid-devel'),
80     ('libcap-dev', 'libcap-devel'),
81     ('libacl1-dev', 'libacl-devel'),
82     ('libattr1-dev', 'libattr-devel'),
83
84     # libNAME1-dev, NAME2-devel
85     ('libpopt-dev', 'popt-devel'),
86     ('libreadline-dev', 'readline-devel'),
87     ('libjansson-dev', 'jansson-devel'),
88     ('liblmdb-dev', 'lmdb-devel'),
89     ('libncurses5-dev', 'ncurses-devel'),
90     # NOTE: Debian 7+ or Ubuntu 16.04+
91     ('libsystemd-dev', 'systemd-devel'),
92     ('libkrb5-dev', 'krb5-devel'),
93     ('libldap2-dev', 'openldap-devel'),
94     ('libcups2-dev', 'cups-devel'),
95     ('libpam0g-dev', 'pam-devel'),
96     ('libgpgme11-dev', 'gpgme-devel'),
97     # NOTE: Debian 8+ and Ubuntu 14.04+
98     ('libgnutls28-dev', 'gnutls-devel'),
99     ('libtasn1-bin', 'libtasn1-tools'),
100     ('libtasn1-dev', 'libtasn1-devel'),
101     ('', 'quota-devel'),
102     ('uuid-dev', 'libuuid-devel'),
103     ('libjs-jquery', ''),
104     ('libavahi-common-dev', 'avahi-devel'),
105     ('libdbus-1-dev', 'dbus-devel'),
106     ('libpcap-dev', 'libpcap-devel'),
107     ('libunwind-dev', 'libunwind-devel'),  # for back trace
108     ('libglib2.0-dev', 'glib2-devel'),
109     ('libicu-dev', 'libicu-devel'),
110     ('heimdal-multidev', ''),
111
112     # NAME1, NAME2
113     # for debian, locales provide locale support with language packs
114     # ubuntu split language packs to language-pack-xx
115     # for centos, glibc-common provide locale support with language packs
116     # fedora split language packs  to glibc-langpack-xx
117     ('locales', 'glibc-common'),  # required for locale
118     ('language-pack-en', 'glibc-langpack-en'),  # we need en_US.UTF-8
119     ('bind9utils', 'bind-utils'),
120     ('dnsutils', ''),
121     ('xsltproc', 'libxslt'),
122     ('krb5-user', 'krb5-workstation'),
123     ('krb5-config', ''),
124     ('krb5-kdc', 'krb5-server'),
125     ('apt-utils', 'yum-utils'),
126     ('pkg-config', 'pkgconfig'),
127     ('procps', 'procps-ng'),  # required for the free cmd in tests
128     ('lsb-release', 'lsb-release'),  # we need lsb_relase to show info
129     ('', 'rpcgen'),  # required for test
130     # refer: https://fedoraproject.org/wiki/Changes/SunRPCRemoval
131     ('', 'libtirpc-devel'),  # for <rpc/rpc.h> header on fedora
132     ('', 'rpcsvc-proto-devel'), # for <rpcsvc/rquota.h> header
133     ('mawk', 'gawk'),
134     ('', 'mold'),
135
136     ('python3', 'python3'),
137     ('python3-cryptography', 'python3-cryptography'), # for krb5 tests
138     ('python3-dev', 'python3-devel'),
139     ('python3-dbg', ''),
140     ('python3-iso8601', 'python3-iso8601'),
141     ('python3-gpg', 'python3-gpg'),  # defaults to ubuntu/fedora latest
142     ('python3-markdown', 'python3-markdown'),
143     ('python3-dnspython', 'python3-dns'),
144     ('python3-pexpect', ''),  # for wintest only
145     ('python3-pyasn1', 'python3-pyasn1'), # for krb5 tests
146     ('python3-setproctitle', 'python3-setproctitle'),
147     ('python3-requests', 'python3-requests'), # for cert auto enroll
148
149     ('', 'python3-libsemanage'),
150     ('', 'python3-policycoreutils'),
151
152     # perl
153     ('libparse-yapp-perl', 'perl-Parse-Yapp'),
154     ('libjson-perl', 'perl-JSON'),
155     ('', 'perl-JSON-Parse'),
156     ('perl-modules', ''),
157     ('', 'perl-FindBin'),
158     ('', 'perl-Archive-Tar'),
159     ('', 'perl-ExtUtils-MakeMaker'),
160     ('', 'perl-Test-Base'),
161     ('', 'perl-generators'),
162     ('', 'perl-interpreter'),
163
164     # fs
165     ('xfslibs-dev', 'xfsprogs-devel'), # for xfs quota support
166     ('', 'glusterfs-api-devel'),
167     ('glusterfs-common', 'glusterfs-devel'),
168     ('libcephfs-dev', 'libcephfs-devel'),
169
170     # spotlight
171     ('libtracker-sparql-2.0-dev', 'tracker-devel'),
172
173     # misc
174     # @ means group for rpm, use fedora as rpm default
175     ('build-essential', '@development-tools'),
176     ('debhelper', ''),
177     # rpm has no pkg for docbook-xml
178     ('docbook-xml', 'docbook-dtds'),
179     ('docbook-xsl', 'docbook-style-xsl'),
180     ('', 'keyutils-libs-devel'),
181     ('', 'which'),
182 ]
183
184
185 DEB_PKGS = COMMON + [pkg for pkg, _ in PKGS if pkg]
186 RPM_PKGS = COMMON + [pkg for _, pkg in PKGS if pkg]
187
188 GENERATED_MARKER = r"""
189 #
190 # This file is generated by 'bootstrap/template.py --render'
191 # See also bootstrap/config.py
192 #
193 """
194
195
196 APT_BOOTSTRAP = r"""
197 #!/bin/bash
198 {GENERATED_MARKER}
199 set -xueo pipefail
200
201 export DEBIAN_FRONTEND=noninteractive
202 apt-get -y update
203
204 apt-get -y install \
205     {pkgs}
206
207 apt-get -y autoremove
208 apt-get -y autoclean
209 apt-get -y clean
210 """
211
212
213 YUM_BOOTSTRAP = r"""
214 #!/bin/bash
215 {GENERATED_MARKER}
216 set -xueo pipefail
217
218 yum update -y
219 yum install -y epel-release
220 yum install -y yum-plugin-copr
221 yum copr enable -y sergiomb/SambaAD
222 yum update -y
223
224 yum install -y \
225     {pkgs}
226
227 yum clean all
228
229 if [ ! -f /usr/bin/python3 ]; then
230     ln -sf /usr/bin/python3.6 /usr/bin/python3
231 fi
232 """
233
234 CENTOS8S_YUM_BOOTSTRAP = r"""
235 #!/bin/bash
236 {GENERATED_MARKER}
237 set -xueo pipefail
238
239 yum update -y
240 yum install -y dnf-plugins-core
241 yum install -y epel-release
242
243 yum -v repolist all
244 yum config-manager --set-enabled powertools -y || \
245     yum config-manager --set-enabled powertools -y
246
247 yum update -y
248
249 yum install -y \
250     --setopt=install_weak_deps=False \
251     {pkgs}
252
253 yum clean all
254 """
255
256 DNF_BOOTSTRAP = r"""
257 #!/bin/bash
258 {GENERATED_MARKER}
259 set -xueo pipefail
260
261 dnf update -y
262
263 dnf install -y \
264     --setopt=install_weak_deps=False \
265     {pkgs}
266
267 dnf clean all
268 """
269
270 DNF_BOOTSTRAP_MIT = r"""
271 #!/bin/bash
272 {GENERATED_MARKER}
273 set -xueo pipefail
274
275 dnf update -y
276 dnf install -y dnf-plugins-core
277 dnf copr -y enable abbra/krb5-test
278 dnf update -y
279
280 dnf install -y \
281     --setopt=install_weak_deps=False \
282     {pkgs}
283
284 dnf clean all
285 """
286
287 ZYPPER_BOOTSTRAP = r"""
288 #!/bin/bash
289 {GENERATED_MARKER}
290 set -xueo pipefail
291
292 zypper --non-interactive refresh
293 zypper --non-interactive update
294 zypper --non-interactive install \
295     --no-recommends \
296     system-user-nobody \
297     {pkgs}
298
299 zypper --non-interactive clean
300
301 if [ -f /usr/lib/mit/bin/krb5-config ]; then
302     ln -sf /usr/lib/mit/bin/krb5-config /usr/bin/krb5-config
303 fi
304 """
305
306 # A generic shell script to setup locale
307 LOCALE_SETUP = r"""
308 #!/bin/bash
309 {GENERATED_MARKER}
310 set -xueo pipefail
311
312 # refer to /usr/share/i18n/locales
313 INPUTFILE=en_US
314 # refer to /usr/share/i18n/charmaps
315 CHARMAP=UTF-8
316 # locale to generate in /usr/lib/locale
317 # glibc/localedef will normalize UTF-8 to utf8, follow the naming style
318 LOCALE=$INPUTFILE.utf8
319
320 # if locale is already correct, exit
321 ( locale | grep LC_ALL | grep -i $LOCALE ) && exit 0
322
323 # if locale not available, generate locale into /usr/lib/locale
324 if ! ( locale --all-locales | grep -i $LOCALE )
325 then
326     # no-archive means create its own dir
327     localedef --inputfile $INPUTFILE --charmap $CHARMAP --no-archive $LOCALE
328 fi
329
330 # update locale conf and global env file
331 # set both LC_ALL and LANG for safe
332
333 # update conf for Debian family
334 FILE=/etc/default/locale
335 if [ -f $FILE ]
336 then
337     echo LC_ALL="$LOCALE" > $FILE
338     echo LANG="$LOCALE" >> $FILE
339 fi
340
341 # update conf for RedHat family
342 FILE=/etc/locale.conf
343 if [ -f $FILE ]
344 then
345     # LC_ALL is not valid in this file, set LANG only
346     echo LANG="$LOCALE" > $FILE
347 fi
348
349 # update global env file
350 FILE=/etc/environment
351 if [ -f $FILE ]
352 then
353     # append LC_ALL if not exist
354     grep LC_ALL $FILE || echo LC_ALL="$LOCALE" >> $FILE
355     # append LANG if not exist
356     grep LANG $FILE || echo LANG="$LOCALE" >> $FILE
357 fi
358 """
359
360
361 DOCKERFILE = r"""
362 {GENERATED_MARKER}
363 FROM {docker_image}
364
365 # pass in with --build-arg while build
366 ARG SHA1SUM
367 RUN [ -n $SHA1SUM ] && echo $SHA1SUM > /sha1sum.txt
368
369 ADD *.sh /tmp/
370 # need root permission, do it before USER samba
371 RUN /tmp/bootstrap.sh && /tmp/locale.sh
372
373 # if ld.gold exists, force link it to ld
374 RUN set -x; LD=$(which ld); LD_GOLD=$(which ld.gold); test -x $LD_GOLD && ln -sf $LD_GOLD $LD && test -x $LD && echo "$LD is now $LD_GOLD"
375 # if ld.mold exists, force link it to ld (prefer mold over gold! ;-)
376 RUN set -x; LD=$(which ld); LD_MOLD=$(which ld.mold); test -x $LD_MOLD && ln -sf $LD_MOLD $LD && test -x $LD && echo "$LD is now $LD_MOLD"
377
378 # make test can not work with root, so we have to create a new user
379 RUN useradd -m -U -s /bin/bash samba && \
380     mkdir -p /etc/sudoers.d && \
381     echo "samba ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/samba
382
383 USER samba
384 WORKDIR /home/samba
385 # samba tests rely on this
386 ENV USER=samba LC_ALL=en_US.utf8 LANG=en_US.utf8
387 """
388
389 # Vagrantfile snippet for each dist
390 VAGRANTFILE_SNIPPET = r"""
391     config.vm.define "{name}" do |v|
392         v.vm.box = "{vagrant_box}"
393         v.vm.hostname = "{name}"
394         v.vm.provision :shell, path: "{name}/bootstrap.sh"
395         v.vm.provision :shell, path: "{name}/locale.sh"
396     end
397 """
398
399 # global Vagrantfile with snippets for all dists
400 VAGRANTFILE_GLOBAL = r"""
401 {GENERATED_MARKER}
402
403 Vagrant.configure("2") do |config|
404     config.ssh.insert_key = false
405
406 {vagrantfile_snippets}
407
408 end
409 """
410
411
412 DEB_DISTS = {
413     'debian11': {
414         'docker_image': 'debian:11',
415         'vagrant_box': 'debian/bullseye64',
416         'replace': {
417             'language-pack-en': '',   # included in locales
418         }
419     },
420     'ubuntu1804': {
421         'docker_image': 'ubuntu:18.04',
422         'vagrant_box': 'ubuntu/bionic64',
423         'replace': {
424             'liburing-dev': '',   # not available
425         }
426     },
427     'ubuntu2004': {
428         'docker_image': 'ubuntu:20.04',
429         'vagrant_box': 'ubuntu/focal64',
430         'replace': {
431             'liburing-dev': '',   # not available
432         }
433     },
434 }
435
436
437 RPM_DISTS = {
438     'centos7': {
439         'docker_image': 'centos:7',
440         'vagrant_box': 'centos/7',
441         'bootstrap': YUM_BOOTSTRAP,
442         'replace': {
443             'lsb-release': 'redhat-lsb',
444             'python3': 'python36',
445             'python3-cryptography': 'python36-cryptography',
446             'python3-devel': 'python36-devel',
447             'python3-dns': 'python36-dns',
448             'python3-pyasn1': 'python36-pyasn1',
449             'python3-gpg': 'python36-gpg',
450             'python3-iso8601' : 'python36-iso8601',
451             'python3-markdown': 'python36-markdown',
452             'python3-requests': 'python36-requests',
453             # although python36-devel is available
454             # after epel-release installed
455             # however, all other python3 pkgs are still python36-ish
456             'python2-gpg': 'pygpgme',
457             'python3-gpg': '',  # no python3-gpg yet
458             '@development-tools': '"@Development Tools"',  # add quotes
459             'glibc-langpack-en': '',  # included in glibc-common
460             'glibc-locale-source': '',  # included in glibc-common
461             # update perl core modules on centos
462             # fix: Can't locate Archive/Tar.pm in @INC
463             'perl': 'perl-core',
464             'perl-FindBin': '',
465             'rpcsvc-proto-devel': '',
466             'glusterfs-api-devel': '',
467             'glusterfs-devel': '',
468             'libcephfs-devel': '',
469             'gnutls-devel': 'compat-gnutls37-devel',
470             'liburing-devel': '',   # not available
471             'python3-setproctitle': 'python36-setproctitle',
472             'tracker-devel': '', # do not install
473             'mold': '',
474         }
475     },
476     'centos8s': {
477         'docker_image': 'quay.io/centos/centos:stream8',
478         'vagrant_box': 'centos/stream8',
479         'bootstrap': CENTOS8S_YUM_BOOTSTRAP,
480         'replace': {
481             'lsb-release': 'redhat-lsb',
482             '@development-tools': '"@Development Tools"',  # add quotes
483             'lcov': '', # does not exist
484             'perl-JSON-Parse': '', # does not exist?
485             'perl-Test-Base': 'perl-Test-Simple',
486             'perl-FindBin': '',
487             'liburing-devel': '', # not available yet, Add me back, once available!
488             'mold': '',
489         }
490     },
491     'fedora36': {
492         'docker_image': 'fedora:36',
493         'vagrant_box': 'fedora/36-cloud-base',
494         'bootstrap': DNF_BOOTSTRAP,
495         'replace': {
496             'lsb-release': 'redhat-lsb',
497             'perl-FindBin': '',
498             'python3-iso8601': 'python3-dateutil',
499             'libtracker-sparql-2.0-dev': '',  # only tracker 3.x is available
500         }
501     },
502     'f36mit120': {
503         'docker_image': 'fedora:36',
504         'vagrant_box': 'fedora/36-cloud-base',
505         'bootstrap': DNF_BOOTSTRAP_MIT,
506         'replace': {
507             'lsb-release': 'redhat-lsb',
508             'perl-FindBin': '',
509             'python3-iso8601': 'python3-dateutil',
510             'libtracker-sparql-2.0-dev': '',  # only tracker 3.x is available
511         }
512     },
513     'opensuse153': {
514         'docker_image': 'opensuse/leap:15.3',
515         'vagrant_box': 'opensuse/openSUSE-15.3-x86_64',
516         'bootstrap': ZYPPER_BOOTSTRAP,
517         'replace': {
518             '@development-tools': '',
519             'dbus-devel': 'dbus-1-devel',
520             'docbook-style-xsl': 'docbook-xsl-stylesheets',
521             'glibc-common': 'glibc-locale',
522             'glibc-locale-source': 'glibc-i18ndata',
523             'glibc-langpack-en': '',
524             'jansson-devel': 'libjansson-devel',
525             'keyutils-libs-devel': 'keyutils-devel',
526             'krb5-workstation': 'krb5-client',
527             'python3-libsemanage': 'python2-semanage',
528             'openldap-devel': 'openldap2-devel',
529             'perl-Archive-Tar': 'perl-Archive-Tar-Wrapper',
530             'perl-JSON-Parse': 'perl-JSON-XS',
531             'perl-generators': '',
532             'perl-interpreter': '',
533             'perl-FindBin': '',
534             'procps-ng': 'procps',
535             'python3-iso8601': 'python3-python-dateutil',
536             'python3-dns': 'python3-dnspython',
537             'python3-markdown': 'python3-Markdown',
538             'quota-devel': '',
539             'glusterfs-api-devel': '',
540             'libtasn1-tools': '', # asn1Parser is part of libtasn1
541             'mold': '',
542         }
543     }
544 }
545
546
547 DEB_FAMILY = {
548     'name': 'deb',
549     'pkgs': DEB_PKGS,
550     'bootstrap': APT_BOOTSTRAP,  # family default
551     'dists': DEB_DISTS,
552 }
553
554
555 RPM_FAMILY = {
556     'name': 'rpm',
557     'pkgs': RPM_PKGS,
558     'bootstrap': YUM_BOOTSTRAP,  # family default
559     'dists': RPM_DISTS,
560 }
561
562
563 YML_HEADER = r"""
564 ---
565 packages:
566 """
567
568
569 def expand_family_dists(family):
570     dists = {}
571     for name, config in family['dists'].items():
572         config = config.copy()
573         config['name'] = name
574         config['home'] = join(OUT, name)
575         config['family'] = family['name']
576         config['GENERATED_MARKER'] = GENERATED_MARKER
577
578         # replace dist specific pkgs
579         replace = config.get('replace', {})
580         pkgs = []
581         for pkg in family['pkgs']:
582             pkg = replace.get(pkg, pkg)  # replace if exists or get self
583             if pkg:
584                 pkgs.append(pkg)
585         pkgs.sort()
586
587         lines = ['  - {}'.format(pkg) for pkg in pkgs]
588         config['packages.yml'] = YML_HEADER.lstrip() + os.linesep.join(lines)
589
590         sep = ' \\' + os.linesep + '    '
591         config['pkgs'] = sep.join(pkgs)
592
593         # get dist bootstrap template or fall back to family default
594         bootstrap_template = config.get('bootstrap', family['bootstrap'])
595         config['bootstrap.sh'] = bootstrap_template.format(**config).strip()
596         config['locale.sh'] = LOCALE_SETUP.format(**config).strip()
597
598         config['Dockerfile'] = DOCKERFILE.format(**config).strip()
599         # keep the indent, no strip
600         config['vagrantfile_snippet'] = VAGRANTFILE_SNIPPET.format(**config)
601
602         dists[name] = config
603     return dists
604
605
606 # expanded config for dists
607 DEB_DISTS_EXP = expand_family_dists(DEB_FAMILY)
608 RPM_DISTS_EXP = expand_family_dists(RPM_FAMILY)
609
610 # assemble all together
611 DISTS = {}
612 DISTS.update(DEB_DISTS_EXP)
613 DISTS.update(RPM_DISTS_EXP)
614
615
616 def render_vagrantfile(dists):
617     """
618     Render all snippets for each dist into global Vagrantfile.
619
620     Vagrant supports multiple vms in one Vagrantfile.
621     This make it easier to manage the fleet, e.g:
622
623     start all: vagrant up
624     start one: vagrant up ubuntu1804
625
626     All other commands apply to above syntax, e.g.: status, destroy, provision
627     """
628     # sort dists by name and put all vagrantfile snippets together
629     snippets = [
630         dists[dist]['vagrantfile_snippet']
631         for dist in sorted(dists.keys())]
632
633     return VAGRANTFILE_GLOBAL.format(
634             vagrantfile_snippets=''.join(snippets),
635             GENERATED_MARKER=GENERATED_MARKER
636             )
637
638
639 VAGRANTFILE = render_vagrantfile(DISTS)
640
641
642 # data we need to expose
643 __all__ = ['DISTS', 'VAGRANTFILE', 'OUT']