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