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