c0f3b37cfbfff16778d581d7fa1de524f97df109
[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-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     {pkgs}
232
233 dnf clean all
234 """
235
236 ZYPPER_BOOTSTRAP = r"""
237 #!/bin/bash
238 {GENERATED_MARKER}
239 set -xueo pipefail
240
241 zypper --non-interactive refresh
242 zypper --non-interactive update
243 zypper --non-interactive install \
244     {pkgs} \
245     system-user-nobody
246
247 zypper --non-interactive clean
248
249 if [ -f /usr/lib/mit/bin/krb5-config ]; then
250     ln -sf /usr/lib/mit/bin/krb5-config /usr/bin/krb5-config
251 fi
252 """
253
254 # A generic shell script to setup locale
255 LOCALE_SETUP = r"""
256 #!/bin/bash
257 {GENERATED_MARKER}
258 set -xueo pipefail
259
260 # refer to /usr/share/i18n/locales
261 INPUTFILE=en_US
262 # refer to /usr/share/i18n/charmaps
263 CHARMAP=UTF-8
264 # locale to generate in /usr/lib/locale
265 # glibc/localedef will normalize UTF-8 to utf8, follow the naming style
266 LOCALE=$INPUTFILE.utf8
267
268 # if locale is already correct, exit
269 ( locale | grep LC_ALL | grep -i $LOCALE ) && exit 0
270
271 # if locale not available, generate locale into /usr/lib/locale
272 if ! ( locale --all-locales | grep -i $LOCALE )
273 then
274     # no-archive means create its own dir
275     localedef --inputfile $INPUTFILE --charmap $CHARMAP --no-archive $LOCALE
276 fi
277
278 # update locale conf and global env file
279 # set both LC_ALL and LANG for safe
280
281 # update conf for Debian family
282 FILE=/etc/default/locale
283 if [ -f $FILE ]
284 then
285     echo LC_ALL="$LOCALE" > $FILE
286     echo LANG="$LOCALE" >> $FILE
287 fi
288
289 # update conf for RedHat family
290 FILE=/etc/locale.conf
291 if [ -f $FILE ]
292 then
293     # LC_ALL is not valid in this file, set LANG only
294     echo LANG="$LOCALE" > $FILE
295 fi
296
297 # update global env file
298 FILE=/etc/environment
299 if [ -f $FILE ]
300 then
301     # append LC_ALL if not exist
302     grep LC_ALL $FILE || echo LC_ALL="$LOCALE" >> $FILE
303     # append LANG if not exist
304     grep LANG $FILE || echo LANG="$LOCALE" >> $FILE
305 fi
306 """
307
308
309 DOCKERFILE = r"""
310 {GENERATED_MARKER}
311 FROM {docker_image}
312
313 # pass in with --build-arg while build
314 ARG SHA1SUM
315 RUN [ -n $SHA1SUM ] && echo $SHA1SUM > /sha1sum.txt
316
317 ADD *.sh /tmp/
318 # need root permission, do it before USER samba
319 RUN /tmp/bootstrap.sh && /tmp/locale.sh
320
321 # if ld.gold exists, force link it to ld
322 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"
323
324 # make test can not work with root, so we have to create a new user
325 RUN useradd -m -U -s /bin/bash samba && \
326     mkdir -p /etc/sudoers.d && \
327     echo "samba ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/samba
328
329 USER samba
330 WORKDIR /home/samba
331 # samba tests rely on this
332 ENV USER=samba LC_ALL=en_US.utf8 LANG=en_US.utf8
333 """
334
335 # Vagrantfile snippet for each dist
336 VAGRANTFILE_SNIPPET = r"""
337     config.vm.define "{name}" do |v|
338         v.vm.box = "{vagrant_box}"
339         v.vm.hostname = "{name}"
340         v.vm.provision :shell, path: "{name}/bootstrap.sh"
341         v.vm.provision :shell, path: "{name}/locale.sh"
342     end
343 """
344
345 # global Vagrantfile with snippets for all dists
346 VAGRANTFILE_GLOBAL = r"""
347 {GENERATED_MARKER}
348
349 Vagrant.configure("2") do |config|
350     config.ssh.insert_key = false
351
352 {vagrantfile_snippets}
353
354 end
355 """
356
357
358 DEB_DISTS = {
359     'debian7': {
360         'docker_image': 'debian:7',
361         'vagrant_box': 'debian/wheezy64',
362         'replace': {
363             'libgnutls28-dev': 'libgnutls-dev',
364             'libsystemd-dev': '',  # not available, remove
365             'lmdb-utils': '',  # not available, remove
366             'liblmdb-dev': '',  # not available, remove
367             'python-gpg': 'python-gpgme',
368             'python3-gpg': '',  # no python3 gpg pkg available, remove
369             'language-pack-en': '',   # included in locales
370         }
371     },
372     'debian8': {
373         'docker_image': 'debian:8',
374         'vagrant_box': 'debian/jessie64',
375         'replace': {
376             'python-gpg': 'python-gpgme',
377             'python3-gpg': 'python3-gpgme',
378             'language-pack-en': '',   # included in locales
379         }
380     },
381     'debian9': {
382         'docker_image': 'debian:9',
383         'vagrant_box': 'debian/stretch64',
384         'replace': {
385             'language-pack-en': '',   # included in locales
386         }
387     },
388     'ubuntu1404': {
389         'docker_image': 'ubuntu:14.04',
390         'vagrant_box': 'ubuntu/trusty64',
391         'replace': {
392             'libsystemd-dev': '',  # remove
393             'libgnutls28-dev': 'libgnutls-dev',
394             'python-gpg': 'python-gpgme',
395             'python3-gpg': 'python3-gpgme',
396             'lmdb-utils': 'lmdb-utils/trusty-backports',
397             'liblmdb-dev': 'liblmdb-dev/trusty-backports',
398             'libunwind-dev': 'libunwind8-dev',
399             'glusterfs-common': '',
400             'libcephfs-dev': '',
401         }
402     },
403     'ubuntu1604': {
404         'docker_image': 'ubuntu:16.04',
405         'vagrant_box': 'ubuntu/xenial64',
406         'replace': {
407             'python-gpg': 'python-gpgme',
408             'python3-gpg': 'python3-gpgme',
409             'glusterfs-common': '',
410             'libcephfs-dev': '',
411         }
412     },
413     'ubuntu1804': {
414         'docker_image': 'ubuntu:18.04',
415         'vagrant_box': 'ubuntu/bionic64',
416     },
417 }
418
419
420 RPM_DISTS = {
421     'centos6': {
422         'docker_image': 'centos:6',
423         'vagrant_box': 'centos/6',
424         'bootstrap': YUM_BOOTSTRAP,
425         'replace': {
426             'lsb-release': 'redhat-lsb',
427             'python3-devel': 'python34-devel',
428             'python2-gpg': 'pygpgme',
429             'python3-gpg': '',  # no python3-gpg yet
430             '@development-tools': '"@Development Tools"',  # add quotes
431             'glibc-langpack-en': '',  # included in glibc-common
432             'glibc-locale-source': '',  # included in glibc-common
433             'procps-ng': 'procps',  # centos6 still use old name
434             # update perl core modules on centos
435             # fix: Can't locate Archive/Tar.pm in @INC
436             'perl': 'perl-core',
437             'rpcsvc-proto-devel': '',
438             'glusterfs-api-devel': '',
439             'glusterfs-devel': '',
440             'libcephfs-devel': '',
441         }
442     },
443     'centos7': {
444         'docker_image': 'centos:7',
445         'vagrant_box': 'centos/7',
446         'bootstrap': YUM_BOOTSTRAP,
447         'replace': {
448             'lsb-release': 'redhat-lsb',
449             'python3-devel': 'python34-devel',
450             # although python36-devel is available
451             # after epel-release installed
452             # however, all other python3 pkgs are still python34-ish
453             'python2-gpg': 'pygpgme',
454             'python3-gpg': '',  # no python3-gpg yet
455             '@development-tools': '"@Development Tools"',  # add quotes
456             'glibc-langpack-en': '',  # included in glibc-common
457             'glibc-locale-source': '',  # included in glibc-common
458             # update perl core modules on centos
459             # fix: Can't locate Archive/Tar.pm in @INC
460             'perl': 'perl-core',
461             'rpcsvc-proto-devel': '',
462             'glusterfs-api-devel': '',
463             'glusterfs-devel': '',
464             'libcephfs-devel': '',
465         }
466     },
467     'fedora28': {
468         'docker_image': 'fedora:28',
469         'vagrant_box': 'fedora/28-cloud-base',
470         'bootstrap': DNF_BOOTSTRAP,
471         'replace': {
472             'lsb-release': 'redhat-lsb',
473         }
474     },
475     'fedora29': {
476         'docker_image': 'fedora:29',
477         'vagrant_box': 'fedora/29-cloud-base',
478         'bootstrap': DNF_BOOTSTRAP,
479         'replace': {
480             'lsb-release': 'redhat-lsb',
481         }
482     },
483     'opensuse150': {
484         'docker_image': 'opensuse/leap:15.0',
485         'vagrant_box': 'opensuse/openSUSE-15.0-x86_64',
486         'bootstrap': ZYPPER_BOOTSTRAP,
487         'replace': {
488             '@development-tools': '',
489             'dbus-devel': 'dbus-1-devel',
490             'docbook-style-xsl': 'docbook-xsl-stylesheets',
491             'glibc-common': 'glibc-locale',
492             'glibc-locale-source': 'glibc-i18ndata',
493             'glibc-langpack-en': '',
494             'jansson-devel': 'libjansson-devel',
495             'keyutils-libs-devel': 'keyutils-devel',
496             'krb5-workstation': 'krb5-client',
497             'libnsl2-devel': 'libnsl-devel',
498             'libsemanage-python': 'python2-semanage',
499             'nettle-devel': 'libnettle-devel',
500             'openldap-devel': 'openldap2-devel',
501             'perl-Archive-Tar': 'perl-Archive-Tar-Wrapper',
502             'perl-JSON-Parse': 'perl-JSON-XS',
503             'perl-generators': '',
504             'perl-interpreter': '',
505             'procps-ng': 'procps',
506             'python-dns': 'python2-dnspython',
507             'python3-crypto': 'python3-pycrypto',
508             'python3-dns': 'python3-dnspython',
509             'python3-markdown': 'python3-Markdown',
510             'quota-devel': '',
511             'glusterfs-api-devel': '',
512         }
513     }
514 }
515
516
517 DEB_FAMILY = {
518     'name': 'deb',
519     'pkgs': DEB_PKGS,
520     'bootstrap': APT_BOOTSTRAP,  # family default
521     'dists': DEB_DISTS,
522 }
523
524
525 RPM_FAMILY = {
526     'name': 'rpm',
527     'pkgs': RPM_PKGS,
528     'bootstrap': YUM_BOOTSTRAP,  # family default
529     'dists': RPM_DISTS,
530 }
531
532
533 YML_HEADER = r"""
534 ---
535 packages:
536 """
537
538
539 def expand_family_dists(family):
540     dists = {}
541     for name, config in family['dists'].items():
542         config = config.copy()
543         config['name'] = name
544         config['home'] = join(OUT, name)
545         config['family'] = family['name']
546         config['GENERATED_MARKER'] = GENERATED_MARKER
547
548         # replace dist specific pkgs
549         replace = config.get('replace', {})
550         pkgs = []
551         for pkg in family['pkgs']:
552             pkg = replace.get(pkg, pkg)  # replace if exists or get self
553             if pkg:
554                 pkgs.append(pkg)
555         pkgs.sort()
556
557         lines = ['  - {}'.format(pkg) for pkg in pkgs]
558         config['packages.yml'] = YML_HEADER.lstrip() + os.linesep.join(lines)
559
560         sep = ' \\' + os.linesep + '    '
561         config['pkgs'] = sep.join(pkgs)
562
563         # get dist bootstrap template or fall back to family default
564         bootstrap_template = config.get('bootstrap', family['bootstrap'])
565         config['bootstrap.sh'] = bootstrap_template.format(**config).strip()
566         config['locale.sh'] = LOCALE_SETUP.format(**config).strip()
567
568         config['Dockerfile'] = DOCKERFILE.format(**config).strip()
569         # keep the indent, no strip
570         config['vagrantfile_snippet'] = VAGRANTFILE_SNIPPET.format(**config)
571
572         dists[name] = config
573     return dists
574
575
576 # expanded config for dists
577 DEB_DISTS_EXP = expand_family_dists(DEB_FAMILY)
578 RPM_DISTS_EXP = expand_family_dists(RPM_FAMILY)
579
580 # assemble all together
581 DISTS = {}
582 DISTS.update(DEB_DISTS_EXP)
583 DISTS.update(RPM_DISTS_EXP)
584
585
586 def render_vagrantfile(dists):
587     """
588     Render all snippets for each dist into global Vagrantfile.
589
590     Vagrant supports multiple vms in one Vagrantfile.
591     This make it easier to manage the fleet, e.g:
592
593     start all: vagrant up
594     start one: vagrant up ubuntu1804
595
596     All other commands apply to above syntax, e.g.: status, destroy, provision
597     """
598     # sort dists by name and put all vagrantfile snippets together
599     snippets = [
600         dists[dist]['vagrantfile_snippet']
601         for dist in sorted(dists.keys())]
602
603     return VAGRANTFILE_GLOBAL.format(
604             vagrantfile_snippets=''.join(snippets),
605             GENERATED_MARKER=GENERATED_MARKER
606             )
607
608
609 VAGRANTFILE = render_vagrantfile(DISTS)
610
611
612 # data we need to expose
613 __all__ = ['DISTS', 'VAGRANTFILE', 'OUT']