bootstrap/config.py: Add gzip, which and hostname to base packages
[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', 'redhat-lsb'),  # 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             'python3-devel': 'python34-devel',
385             'python2-gpg': 'pygpgme',
386             'python3-gpg': '',  # no python3-gpg yet
387             '@development-tools': '"@Development Tools"',  # add quotes
388             'glibc-langpack-en': '',  # included in glibc-common
389             'glibc-locale-source': '',  # included in glibc-common
390             'procps-ng': 'procps',  # centos6 still use old name
391             # update perl core modules on centos
392             # fix: Can't locate Archive/Tar.pm in @INC
393             'perl': 'perl-core',
394         }
395     },
396     'centos7': {
397         'docker_image': 'centos:7',
398         'vagrant_box': 'centos/7',
399         'bootstrap': YUM_BOOTSTRAP,
400         'replace': {
401             'python3-devel': 'python34-devel',
402             # although python36-devel is available
403             # after epel-release installed
404             # however, all other python3 pkgs are still python34-ish
405             'python2-gpg': 'pygpgme',
406             'python3-gpg': '',  # no python3-gpg yet
407             '@development-tools': '"@Development Tools"',  # add quotes
408             'glibc-langpack-en': '',  # included in glibc-common
409             'glibc-locale-source': '',  # included in glibc-common
410             # update perl core modules on centos
411             # fix: Can't locate Archive/Tar.pm in @INC
412             'perl': 'perl-core',
413         }
414     },
415     'fedora28': {
416         'docker_image': 'fedora:28',
417         'vagrant_box': 'fedora/28-cloud-base',
418         'bootstrap': DNF_BOOTSTRAP,
419     },
420     'fedora29': {
421         'docker_image': 'fedora:29',
422         'vagrant_box': 'fedora/29-cloud-base',
423         'bootstrap': DNF_BOOTSTRAP,
424     },
425 }
426
427
428 DEB_FAMILY = {
429     'name': 'deb',
430     'pkgs': DEB_PKGS,
431     'bootstrap': APT_BOOTSTRAP,  # family default
432     'dists': DEB_DISTS,
433 }
434
435
436 RPM_FAMILY = {
437     'name': 'rpm',
438     'pkgs': RPM_PKGS,
439     'bootstrap': YUM_BOOTSTRAP,  # family default
440     'dists': RPM_DISTS,
441 }
442
443
444 YML_HEADER = r"""
445 ---
446 packages:
447 """
448
449
450 def expand_family_dists(family):
451     dists = {}
452     for name, config in family['dists'].items():
453         config = config.copy()
454         config['name'] = name
455         config['home'] = join(OUT, name)
456         config['family'] = family['name']
457
458         # replace dist specific pkgs
459         replace = config.get('replace', {})
460         pkgs = []
461         for pkg in family['pkgs']:
462             pkg = replace.get(pkg, pkg)  # replace if exists or get self
463             if pkg:
464                 pkgs.append(pkg)
465         pkgs.sort()
466
467         lines = ['  - {}'.format(pkg) for pkg in pkgs]
468         config['packages.yml'] = YML_HEADER.lstrip() + os.linesep.join(lines)
469
470         sep = ' \\' + os.linesep + '    '
471         config['pkgs'] = sep.join(pkgs)
472
473         # get dist bootstrap template or fall back to family default
474         bootstrap_template = config.get('bootstrap', family['bootstrap'])
475         config['bootstrap.sh'] = bootstrap_template.format(**config).strip()
476         config['locale.sh'] = LOCALE_SETUP.format(**config).strip()
477
478         config['Dockerfile'] = DOCKERFILE.format(**config).strip()
479         # keep the indent, no strip
480         config['vagrantfile_snippet'] = VAGRANTFILE_SNIPPET.format(**config)
481
482         dists[name] = config
483     return dists
484
485
486 # expanded config for dists
487 DEB_DISTS_EXP = expand_family_dists(DEB_FAMILY)
488 RPM_DISTS_EXP = expand_family_dists(RPM_FAMILY)
489
490 # assemble all together
491 DISTS = {}
492 DISTS.update(DEB_DISTS_EXP)
493 DISTS.update(RPM_DISTS_EXP)
494
495
496 def render_vagrantfile(dists):
497     """
498     Render all snippets for each dist into global Vagrantfile.
499
500     Vagrant supports multiple vms in one Vagrantfile.
501     This make it easier to manage the fleet, e.g:
502
503     start all: vagrant up
504     start one: vagrant up ubuntu1804
505
506     All other commands apply to above syntax, e.g.: status, destroy, provision
507     """
508     # sort dists by name and put all vagrantfile snippets together
509     snippets = [
510         dists[dist]['vagrantfile_snippet']
511         for dist in sorted(dists.keys())]
512
513     return VAGRANTFILE_GLOBAL.format(vagrantfile_snippets=''.join(snippets))
514
515
516 VAGRANTFILE = render_vagrantfile(DISTS)
517
518
519 # data we need to expose
520 __all__ = ['DISTS', 'VAGRANTFILE', 'OUT']