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