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