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