]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
tests: packaging
authorLukáš Ježek <lukas.jezek@nic.cz>
Mon, 18 Nov 2019 11:16:42 +0000 (12:16 +0100)
committerPetr Špaček <petr.spacek@nic.cz>
Wed, 20 Nov 2019 12:36:18 +0000 (13:36 +0100)
Directory with subdirectory "packaging" is called "component".

List all components: python3 tests/packaging-doc.py --list
Run all tests/compoments: python3 tests/packaging-doc.py
Run specific test/component: python3 tests/packaging-doc.py --test <component>

The file structure for 1 component:
daemon - dependencies for 1 component "kresd daemon" (default component, must always be there)
scripts/distros - dependencies for 1 component for specific distro (must always be there)
scripts/dockerfile_gen.py - test Dockerfile generator, see below
tests/packaging.py - script to generate and build all combinations
                     of Docker files for all components
[component] - directory of component/test, see below
      (e.g. "client/packaging/", "modules/http/packaging/" etc.)

The file structure of each component:
[component]
<distro>/<version> - package names
- builddeps - list of build depedencies
- rundeps - list of runtime depedencies
- pre-build.sh - script called before build phase
- post-build.sh - script called after build phase
- pre-run.sh - script called before run phase
- post-run.sh - script called after run phase
- install.sh and build.sh script called during build phase
test.config or test.sh - kresd config test or shell script
note: content of "scripts/distroos" is same as "<distro>/<version>" of component.

There are "build" and "run" phases. "build" phase precedes "run" phase.
All script are called in this order:
1. pre-<phase>.sh
2. install packages specifed in the file "<phase>deps"
3a. for "build" phase: run build.sh and install.sh
3b. for "run" phase: run 'kresd -c [component]/test.config' or config.sh
4. remove packages specified in the file "<phase>deps"
5. post-<phase>.sh

Each step above is combines base components with a component under test.
E.g. component "scripts/distros" always precedes component "daemon/packaging"
and it precedes the tested component e.g. "modules/http".

In long term we might migrate this to py.test or some other well known
framework.

57 files changed:
client/packaging/debian/10/builddeps [new file with mode: 0644]
client/packaging/debian/10/rundeps [new file with mode: 0644]
client/packaging/test.sh [new file with mode: 0755]
daemon/packaging/debian/10/builddeps [new file with mode: 0644]
daemon/packaging/debian/10/post-build.sh [new file with mode: 0755]
daemon/packaging/debian/10/post-run.sh [new file with mode: 0755]
daemon/packaging/debian/10/pre-build.sh [new file with mode: 0755]
daemon/packaging/debian/10/pre-run.sh [new file with mode: 0755]
daemon/packaging/debian/10/rundeps [new file with mode: 0644]
daemon/packaging/test.config [new file with mode: 0644]
doc/packaging/debian/10/build.sh [new file with mode: 0755]
doc/packaging/debian/10/builddeps [new file with mode: 0644]
doc/packaging/test.sh [new file with mode: 0755]
modules/bogus_log/packaging/debian/10/rundeps [new file with mode: 0644]
modules/bogus_log/packaging/test.config [new file with mode: 0644]
modules/daf/packaging/test.config [new file with mode: 0644]
modules/detect_time_jump/packaging/test.config [new file with mode: 0644]
modules/detect_time_skew/packaging/test.config [new file with mode: 0644]
modules/dns64/packaging/test.config [new file with mode: 0644]
modules/dnstap/packaging/debian/10/builddeps [new file with mode: 0644]
modules/dnstap/packaging/debian/10/rundeps [new file with mode: 0644]
modules/dnstap/packaging/test.config [new file with mode: 0644]
modules/edns_keepalive/packaging/test.config [new file with mode: 0644]
modules/etcd/packaging/debian/10/builddeps [new file with mode: 0644]
modules/etcd/packaging/debian/10/install.sh [new file with mode: 0755]
modules/etcd/packaging/test.config [new file with mode: 0644]
modules/experimental_dot_auth/packaging/debian/10/rundeps [new file with mode: 0644]
modules/experimental_dot_auth/packaging/test.config [new file with mode: 0644]
modules/graphite/packaging/test.config [new file with mode: 0644]
modules/hints/packaging/test.config [new file with mode: 0644]
modules/http/packaging/debian/10/rundeps [new file with mode: 0644]
modules/http/packaging/test.config [new file with mode: 0644]
modules/nsid/packaging/test.config [new file with mode: 0644]
modules/policy/packaging/test.config [new file with mode: 0644]
modules/predict/packaging/test.config [new file with mode: 0644]
modules/prefill/packaging/test.config [new file with mode: 0644]
modules/priming/packaging/test.config [new file with mode: 0644]
modules/rebinding/packaging/test.config [new file with mode: 0644]
modules/renumber/packaging/test.config [new file with mode: 0644]
modules/serve_stale/packaging/test.config [new file with mode: 0644]
modules/stats/packaging/test.config [new file with mode: 0644]
modules/ta_sentinel/packaging/test.config [new file with mode: 0644]
modules/ta_signal_query/packaging/test.config [new file with mode: 0644]
modules/ta_update/packaging/test.config [new file with mode: 0644]
modules/view/packaging/test.config [new file with mode: 0644]
modules/watchdog/packaging/test.config [new file with mode: 0644]
modules/workarounds/packaging/test.config [new file with mode: 0644]
scripts/distros/debian/10/build.sh [new file with mode: 0755]
scripts/distros/debian/10/install.sh [new file with mode: 0755]
scripts/distros/debian/10/pkg_install [new file with mode: 0644]
scripts/distros/debian/10/pkg_remove [new file with mode: 0644]
scripts/dockerfile_gen.py [new file with mode: 0755]
scripts/make-archive.sh [new file with mode: 0755]
scripts/packaging/debian/10/rundeps [new file with mode: 0644]
tests/packaging-doc.py [new file with mode: 0644]
tests/unit/packaging/debian/10/builddeps [new file with mode: 0644]
tests/unit/packaging/test.sh [new file with mode: 0755]

diff --git a/client/packaging/debian/10/builddeps b/client/packaging/debian/10/builddeps
new file mode 100644 (file)
index 0000000..97d6d15
--- /dev/null
@@ -0,0 +1 @@
+libedit-dev
diff --git a/client/packaging/debian/10/rundeps b/client/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..ae89d1c
--- /dev/null
@@ -0,0 +1 @@
+libedit2
diff --git a/client/packaging/test.sh b/client/packaging/test.sh
new file mode 100755 (executable)
index 0000000..def6064
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+test -e /usr/sbin/kresc
+/usr/sbin/kresc  # command will fail because of invalid parameters
+test "$?" -eq 1  # linker error would have different exit code
diff --git a/daemon/packaging/debian/10/builddeps b/daemon/packaging/debian/10/builddeps
new file mode 100644 (file)
index 0000000..4f96c44
--- /dev/null
@@ -0,0 +1,16 @@
+debhelper
+libcmocka-dev
+libedit-dev
+libgnutls28-dev
+libknot-dev
+liblmdb-dev
+luajit-5.1-dev
+libsystemd-dev
+libuv1-dev
+luajit
+pkg-config
+meson
+doxygen
+python3-breathe
+python3-sphinx
+python3-sphinx-rtd-theme
diff --git a/daemon/packaging/debian/10/post-build.sh b/daemon/packaging/debian/10/post-build.sh
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/daemon/packaging/debian/10/post-run.sh b/daemon/packaging/debian/10/post-run.sh
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/daemon/packaging/debian/10/pre-build.sh b/daemon/packaging/debian/10/pre-build.sh
new file mode 100755 (executable)
index 0000000..5ee5dbd
--- /dev/null
@@ -0,0 +1,9 @@
+# add debian build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/Debian_10/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list
+wget https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Debian_Next/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/packaging/debian/10/pre-run.sh b/daemon/packaging/debian/10/pre-run.sh
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/daemon/packaging/debian/10/rundeps b/daemon/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..fc5b8d0
--- /dev/null
@@ -0,0 +1,18 @@
+adduser
+dns-root-data
+lua-sec
+lua-socket
+lua-filesystem
+systemd
+libc6
+libdnssec7
+libedit2
+libgcc1
+libgnutls30
+libknot10
+liblmdb0
+libluajit-5.1-2
+libstdc++6
+libsystemd0
+libuv1
+libzscanner3
diff --git a/daemon/packaging/test.config b/daemon/packaging/test.config
new file mode 100644 (file)
index 0000000..96bafc5
--- /dev/null
@@ -0,0 +1 @@
+quit()
diff --git a/doc/packaging/debian/10/build.sh b/doc/packaging/debian/10/build.sh
new file mode 100755 (executable)
index 0000000..f247533
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+ninja -C build_packaging doc
diff --git a/doc/packaging/debian/10/builddeps b/doc/packaging/debian/10/builddeps
new file mode 100644 (file)
index 0000000..81b7a5b
--- /dev/null
@@ -0,0 +1,4 @@
+doxygen
+python3-sphinx
+python3-breathe
+python3-sphinx-rtd-theme
diff --git a/doc/packaging/test.sh b/doc/packaging/test.sh
new file mode 100755 (executable)
index 0000000..4e50dee
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+test -e doc/html/index.html
diff --git a/modules/bogus_log/packaging/debian/10/rundeps b/modules/bogus_log/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..c557cb2
--- /dev/null
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/bogus_log/packaging/test.config b/modules/bogus_log/packaging/test.config
new file mode 100644 (file)
index 0000000..dde661b
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('bogus_log')
+assert(bogus_log)
+quit()
diff --git a/modules/daf/packaging/test.config b/modules/daf/packaging/test.config
new file mode 100644 (file)
index 0000000..951b072
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('daf')
+assert(daf)
+quit()
diff --git a/modules/detect_time_jump/packaging/test.config b/modules/detect_time_jump/packaging/test.config
new file mode 100644 (file)
index 0000000..911fc3c
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('detect_time_jump')
+assert(detect_time_jump)
+quit()
diff --git a/modules/detect_time_skew/packaging/test.config b/modules/detect_time_skew/packaging/test.config
new file mode 100644 (file)
index 0000000..ebb9366
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('detect_time_skew')
+assert(detect_time_skew)
+quit()
diff --git a/modules/dns64/packaging/test.config b/modules/dns64/packaging/test.config
new file mode 100644 (file)
index 0000000..6c8650f
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('dns64')
+assert(dns64)
+quit()
diff --git a/modules/dnstap/packaging/debian/10/builddeps b/modules/dnstap/packaging/debian/10/builddeps
new file mode 100644 (file)
index 0000000..417dc04
--- /dev/null
@@ -0,0 +1,3 @@
+libfstrm-dev
+libprotobuf-c-dev
+protobuf-c-compiler
diff --git a/modules/dnstap/packaging/debian/10/rundeps b/modules/dnstap/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..a726e12
--- /dev/null
@@ -0,0 +1,2 @@
+libfstrm0
+libprotobuf-c1
diff --git a/modules/dnstap/packaging/test.config b/modules/dnstap/packaging/test.config
new file mode 100644 (file)
index 0000000..362b199
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('dnstap')
+assert(dnstap)
+quit()
diff --git a/modules/edns_keepalive/packaging/test.config b/modules/edns_keepalive/packaging/test.config
new file mode 100644 (file)
index 0000000..3d824fa
--- /dev/null
@@ -0,0 +1,9 @@
+modules.load('edns_keepalive')
+
+for _,item in ipairs(modules.list()) do
+       if item == "edns_keepalive" then
+               os.exit(0)
+       end
+end
+
+os.exit(1)
diff --git a/modules/etcd/packaging/debian/10/builddeps b/modules/etcd/packaging/debian/10/builddeps
new file mode 100644 (file)
index 0000000..a355a9f
--- /dev/null
@@ -0,0 +1,3 @@
+libssl-dev
+luarocks
+git
diff --git a/modules/etcd/packaging/debian/10/install.sh b/modules/etcd/packaging/debian/10/install.sh
new file mode 100755 (executable)
index 0000000..4df79d9
--- /dev/null
@@ -0,0 +1 @@
+luarocks install etcd --from=https://mah0x211.github.io/rocks/
diff --git a/modules/etcd/packaging/test.config b/modules/etcd/packaging/test.config
new file mode 100644 (file)
index 0000000..701aa86
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('etcd')
+assert(etcd)
+quit()
diff --git a/modules/experimental_dot_auth/packaging/debian/10/rundeps b/modules/experimental_dot_auth/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..36b83e1
--- /dev/null
@@ -0,0 +1 @@
+lua-basexx
diff --git a/modules/experimental_dot_auth/packaging/test.config b/modules/experimental_dot_auth/packaging/test.config
new file mode 100644 (file)
index 0000000..e66cff4
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('experimental_dot_auth')
+assert(experimental_dot_auth)
+quit()
diff --git a/modules/graphite/packaging/test.config b/modules/graphite/packaging/test.config
new file mode 100644 (file)
index 0000000..1c87f4f
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('graphite')
+assert(graphite)
+quit()
diff --git a/modules/hints/packaging/test.config b/modules/hints/packaging/test.config
new file mode 100644 (file)
index 0000000..804e2c1
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('hints')
+assert(hints)
+quit()
diff --git a/modules/http/packaging/debian/10/rundeps b/modules/http/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..c557cb2
--- /dev/null
@@ -0,0 +1 @@
+lua-http
diff --git a/modules/http/packaging/test.config b/modules/http/packaging/test.config
new file mode 100644 (file)
index 0000000..64e9d39
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('http')
+assert(http)
+quit()
diff --git a/modules/nsid/packaging/test.config b/modules/nsid/packaging/test.config
new file mode 100644 (file)
index 0000000..bbef9c7
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('nsid')
+assert(nsid)
+quit()
diff --git a/modules/policy/packaging/test.config b/modules/policy/packaging/test.config
new file mode 100644 (file)
index 0000000..60b81bf
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('policy')
+assert(policy)
+quit()
diff --git a/modules/predict/packaging/test.config b/modules/predict/packaging/test.config
new file mode 100644 (file)
index 0000000..bd70193
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('predict')
+assert(predict)
+quit()
diff --git a/modules/prefill/packaging/test.config b/modules/prefill/packaging/test.config
new file mode 100644 (file)
index 0000000..424160d
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('prefill')
+assert(prefill)
+quit()
diff --git a/modules/priming/packaging/test.config b/modules/priming/packaging/test.config
new file mode 100644 (file)
index 0000000..b5d14b0
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('priming')
+assert(priming)
+quit()
diff --git a/modules/rebinding/packaging/test.config b/modules/rebinding/packaging/test.config
new file mode 100644 (file)
index 0000000..f7472fb
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('rebinding')
+assert(rebinding)
+quit()
diff --git a/modules/renumber/packaging/test.config b/modules/renumber/packaging/test.config
new file mode 100644 (file)
index 0000000..7465e86
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('renumber')
+assert(renumber)
+quit()
diff --git a/modules/serve_stale/packaging/test.config b/modules/serve_stale/packaging/test.config
new file mode 100644 (file)
index 0000000..6e720ea
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('serve_stale')
+assert(serve_stale)
+quit()
diff --git a/modules/stats/packaging/test.config b/modules/stats/packaging/test.config
new file mode 100644 (file)
index 0000000..038670a
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('stats')
+assert(stats)
+quit()
diff --git a/modules/ta_sentinel/packaging/test.config b/modules/ta_sentinel/packaging/test.config
new file mode 100644 (file)
index 0000000..bf0111e
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('ta_sentinel')
+assert(ta_sentinel)
+quit()
diff --git a/modules/ta_signal_query/packaging/test.config b/modules/ta_signal_query/packaging/test.config
new file mode 100644 (file)
index 0000000..58517b5
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('ta_signal_query')
+assert(ta_signal_query)
+quit()
diff --git a/modules/ta_update/packaging/test.config b/modules/ta_update/packaging/test.config
new file mode 100644 (file)
index 0000000..59681ad
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('ta_update')
+assert(ta_update)
+quit()
diff --git a/modules/view/packaging/test.config b/modules/view/packaging/test.config
new file mode 100644 (file)
index 0000000..8d65b2e
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('view')
+assert(view)
+quit()
diff --git a/modules/watchdog/packaging/test.config b/modules/watchdog/packaging/test.config
new file mode 100644 (file)
index 0000000..42be572
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('watchdog')
+assert(watchdog)
+quit()
diff --git a/modules/workarounds/packaging/test.config b/modules/workarounds/packaging/test.config
new file mode 100644 (file)
index 0000000..0bf78ca
--- /dev/null
@@ -0,0 +1,3 @@
+modules.load('workarounds')
+assert(workarounds)
+quit()
diff --git a/scripts/distros/debian/10/build.sh b/scripts/distros/debian/10/build.sh
new file mode 100755 (executable)
index 0000000..a158971
--- /dev/null
@@ -0,0 +1,18 @@
+CFLAGS="$CFLAGS -Wall -pedantic -fno-omit-frame-pointer"
+LDFLAGS="$LDFLAGS -Wl,--as-needed"
+meson build_packaging \
+       --buildtype=plain \
+       --prefix=/usr \
+       --libdir=lib \
+       -Ddoc=enabled \
+       -Dsystemd_files=enabled \
+       -Dclient=enabled \
+       -Dkeyfile_default=/usr/share/dns/root.key \
+       -Droot_hints=/usr/share/dns/root.hints \
+       -Dinstall_kresd_conf=enabled \
+       -Dunit_tests=enabled \
+       -Dc_args="${CFLAGS}" \
+       -Dc_link_args="${LDFLAGS}"
+
+ninja -C build_packaging
+
diff --git a/scripts/distros/debian/10/install.sh b/scripts/distros/debian/10/install.sh
new file mode 100755 (executable)
index 0000000..10669e9
--- /dev/null
@@ -0,0 +1,2 @@
+ninja -C build_packaging install >/dev/null
+
diff --git a/scripts/distros/debian/10/pkg_install b/scripts/distros/debian/10/pkg_install
new file mode 100644 (file)
index 0000000..f207421
--- /dev/null
@@ -0,0 +1 @@
+apt-get install -y
diff --git a/scripts/distros/debian/10/pkg_remove b/scripts/distros/debian/10/pkg_remove
new file mode 100644 (file)
index 0000000..622ae39
--- /dev/null
@@ -0,0 +1 @@
+apt-get remove -y
diff --git a/scripts/dockerfile_gen.py b/scripts/dockerfile_gen.py
new file mode 100755 (executable)
index 0000000..4ce1718
--- /dev/null
@@ -0,0 +1,252 @@
+#!/usr/bin/env python3
+'''
+Generate minimal Dockefile to build, install, run, and test kresd and modules.
+
+It merges data from two sources:
+
+1. Distribution specific commands for package installation etc.
+   These come from distros/ subtree with two-level hierarchy:
+   <distribution name>/<distribution version>
+   The name and version must match respective names of Docker images.
+
+2. Component-specific data like build and run-time dependencies etc.
+   These come from packaging/ subtree of particular component.
+   E.g. data for "daemon" component are in subtree daemon/packaging/.
+   The structure again has structure
+   <distribution name>/<distribution version>.
+   Files common for all distributions (like tests) are right in
+   in packaging/ directory of given component.
+'''
+
+import argparse
+import logging
+from pathlib import Path
+import os
+import sys
+
+
+class TestEnv():
+    '''
+    Abstract way to schedule commands using different interpreters
+
+    Reformat commands for different interpreters, e.g. Dockerfile, BASH, etc.
+    '''
+    def __init__(self, image):
+        self.image = image
+
+    def load_image(self):
+        raise NotImplementedError()
+
+    def run_cmds(self):
+        raise NotImplementedError()
+
+    def __str__(self):
+        raise NotImplementedError()
+
+
+class DockerBuildEnv(TestEnv):
+    '''
+    Schedule commands as part of Docker build (Dockerfile)
+    '''
+    def __init__(self, image, srcdir):
+        super().__init__(image)
+        self.header = 'WORKDIR /root\nCOPY {} /root\n'.format(srcdir)
+
+    def load_image(self):
+        return 'FROM {0}:{1}\n'.format(self.image.name, self.image.version)
+
+    def run_cmds(self):
+        return '\n'.join('RUN {0}'.format(cmd) for cmd in self.image.cmds)
+
+    def __str__(self):
+        return self.load_image() + self.header + self.run_cmds()
+
+
+class Image():
+    '''
+    Abstraction to hide differences between distributions and their versions
+    '''
+    def __init__(self, img_path, name, version):
+        self.img_path = img_path  # scripts/distros/debian/9
+        self.img_relpath = os.path.join(name, version)  # debian/9
+        self.name = name
+        self.version = version
+        self.actions = {}
+        self.cmds = []
+        self._init_cmds()
+        # fill in Dockerfile with image preparation commands
+
+    def _img_path(self, filename):
+        '''Prepend distro-specific path before filename'''
+        return os.path.join(self.img_path, filename)
+
+    def _init_cmds(self):
+        '''
+        Read commands for image modification from
+        '''
+        for cmd in os.listdir(self.img_path):
+            if cmd == 'prep':  # multi-line commands are handled somewhere else
+                continue
+            with open(self._img_path(cmd)) as cmdfile:
+                self.actions[cmd] = cmdfile.read().strip()
+
+    def __str__(self):
+        return '# image: {0}:{1}\n'.format(self.name, self.version) + '\n'.join(self.cmds)
+
+    def action(self, action, arg):
+        '''
+        Schedule action with given argument, e.g. install package
+
+        E.g. action "pkg_install" with argument "gcc" will schedule command
+        read from image-specific file "distro/version/pkg_install" and append
+        argument "arg". Result is like "apt-get install -y gcc".
+        '''
+        self.cmds.append('{0} {1}'.format(self.actions[action], arg))
+
+    def action_arglist(self, action, cmpimgpath, filename):
+        '''
+        Plan single command with argumets equal to content of given text file
+        '''
+        try:
+            with open(os.path.join(cmpimgpath, filename)) as listf:
+                self.action(action, ' '.join(item.strip() for item in listf))
+        except FileNotFoundError:
+            pass
+
+    def cmd(self, cmd):
+        '''Schedule single command'''
+        assert cmd
+        self.cmds.append(cmd)
+
+    def run_script(self, script):
+        '''Shedule script from root directory'''
+        if os.path.isfile(script):
+            self.cmds.append(script)
+
+    def img_script(self, script):
+        '''Schedule script from image's directory'''
+        path = self._img_path(script)
+        assert os.path.isfile(path)
+        self.run_script(path)
+
+
+class Component():
+    '''
+    API for single component of software (daemon etc.) independent on image
+
+    comp_path must contain subtree <distribution name>/<distribution version>
+    with files containing command specific for particular distribution
+
+    image must be Image to work with
+    '''
+    def __init__(self, comp_path, image):
+        self.comp_path = comp_path
+        self.compimg_path = os.path.join(comp_path, image.img_relpath)
+        self.image = image
+        # Some components do not have external depedencies at the moment so
+        # compimg_path may not exist. That is okay, we will just run their tests.
+
+    def _comp_script(self, script):
+        path = os.path.join(self.comp_path, script)
+        if os.path.exists(path):
+            self.image.cmd(path)
+
+    def install_builddeps(self):
+        self.image.run_script(self.compimg_path + '/pre-build.sh')
+        self.image.action_arglist('pkg_install', self.compimg_path, 'builddeps')
+
+    def build(self):
+        self.image.run_script(self.compimg_path + '/build.sh')
+
+    def install(self):
+        self.image.run_script(self.compimg_path + '/install.sh')
+
+    def remove_builddeps(self):
+        self.image.action_arglist('pkg_remove', self.compimg_path, 'builddeps')
+        self.image.run_script(self.compimg_path + '/post-build.sh')
+
+    def install_rundeps(self):
+        self.image.run_script(self.compimg_path + '/pre-run.sh')
+        self.image.action_arglist('pkg_install', self.compimg_path, 'rundeps')
+
+    def test(self):
+        configcmdpath = os.path.join(self.comp_path, 'test.sh')
+        configtestpath = os.path.join(self.comp_path, 'test.config')
+        if os.path.exists(configcmdpath):
+            self._comp_script('test.sh')
+        elif os.path.exists(configtestpath):
+            self.image.cmd('kresd -f 1 -c {}'.format(configtestpath))
+        self.image.run_script(self.compimg_path + '/post-run.sh')
+
+
+def foreach_component(components, action):
+    '''Execute action for each component'''
+    for comp in components:
+        getattr(comp, action)()
+
+def main():
+    logging.basicConfig(level=logging.DEBUG)
+    argparser = argparse.ArgumentParser(
+        formatter_class=argparse.RawTextHelpFormatter,
+        description='''Generate Dockerfile to build/install/test given components.
+
+Examples:
+* Install build deps, build, install, remove build deps, and test kresd daemon:
+ $ {n} debian 9 daemon/packaging > Dockerfile
+
+* Install build and run-time deps to prepare development image:
+ $ find -name packaging | xargs {n} \\
+    --build=false --install=false --remove-builddeps=false --test=false \\
+        debian 9 > Dockerfile
+'''.format(n=sys.argv[0])
+        )
+    argparser.add_argument(
+        '--builddeps', default=True, type=bool, help='default: true')
+    argparser.add_argument(
+        '--build', default=True, type=bool, help='default: true')
+    argparser.add_argument(
+        '--install', default=True, type=bool, help='default: true')
+    argparser.add_argument(
+        '--remove-builddeps', default=True, type=bool, help='default: true')
+    argparser.add_argument(
+        '--rundeps', default=True, type=bool, help='default: true')
+    argparser.add_argument(
+        '--test', default=True, type=bool, help='default: true')
+    argparser.add_argument(
+        '--srcdir', default=os.getcwd(), type=Path,
+        help='directory to copy into new Docker image; default: .')
+    argparser.add_argument(
+        'distro', help='name of distribution image, e.g. "debian"')
+    argparser.add_argument(
+        'version', help='distribution version, e.g. 9')
+    argparser.add_argument(
+        'components', nargs='+',
+        help='one or more components to process; order is respected')
+    args = argparser.parse_args()
+
+    # all paths must be relative to toplevel Git dir
+    if not os.path.exists('.luacheckrc') or not os.path.exists('NEWS'):
+        sys.exit('This script must be executed from top of distribution tree!')
+
+
+    # load images from disk
+    imgpath = os.path.join('scripts/distros', args.distro, args.version)
+    image = Image(imgpath, args.distro, args.version)
+
+    components = [Component(comppath, image) for comppath in args.components]
+    if args.builddeps:
+        foreach_component(components, 'install_builddeps')
+    if args.build:
+        foreach_component(components, 'build')
+    if args.install:
+        foreach_component(components, 'install')
+    if args.remove_builddeps:
+        foreach_component(components, 'remove_builddeps')
+    if args.rundeps:
+        foreach_component(components, 'install_rundeps')
+    if args.test:
+        foreach_component(components, 'test')
+    print(DockerBuildEnv(image, args.srcdir))
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/make-archive.sh b/scripts/make-archive.sh
new file mode 100755 (executable)
index 0000000..1f8b41e
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh -e
+# Create a distribution tarball, like 'make dist' from autotools.
+cd "$(git rev-parse --show-toplevel)"
+ver="$(git describe | sed 's/^v//')"
+test 0 -ne $(git status --porcelain | wc -l) && \
+       echo "Git working tree is dirty, make it clean first" && \
+       exit 1
+git submodule status --recursive | grep -q '^[^ ]' && \
+       echo "Git submodules are dirty, run: git submodule update --recursive --init" && \
+       exit 2
+
+# 'git ls-files --recurse-submodules' works only if modules are initialized
+name="knot-resolver-$ver"
+tar caf "$name.tar.xz" --no-recursion --transform "s|^|$name/|" -- $(git ls-files --recurse-submodules)
+echo "$name.tar.xz"
diff --git a/scripts/packaging/debian/10/rundeps b/scripts/packaging/debian/10/rundeps
new file mode 100644 (file)
index 0000000..c3cd0e3
--- /dev/null
@@ -0,0 +1,10 @@
+bash
+bsdmainutils
+coreutils
+gdb
+git
+luajit
+pkg-config
+sed
+tar
+xz-utils
diff --git a/tests/packaging-doc.py b/tests/packaging-doc.py
new file mode 100644 (file)
index 0000000..bcc8289
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+
+import logging
+import os.path
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+import argparse
+
+
+DISTROS_PATH = Path(os.path.realpath('scripts/distros'))
+GEN_SCRIPT = Path(os.path.realpath('scripts/dockerfile_gen.py'))
+
+
+def unpack(archive, targetdir):
+    '''
+    Prepare workdir for Docker build by unpacking fresh distribution tarball
+    '''
+    logging.debug('unpacking fresh tarball %s into %s', archive, targetdir)
+    subprocess.check_call(['tar', '-C', targetdir, '-xf', archive])
+
+
+def fresh_tarball():
+    try:
+        # make archive so we have clean state to test
+        archive = subprocess.check_output('scripts/make-archive.sh')
+    except subprocess.CalledProcessError as ex:
+        logging.fatal('failed to generate fresh tarball: %s', ex.output)
+        sys.exit(ex.returncode)
+    return os.path.realpath(archive.strip())
+
+
+def get_distro_vers(distro_root):
+    '''
+    return list of (distro, version) pairs found in distro_root
+    '''
+    # transform list of paths like TOP/debian/9 into (debian, 9)
+    dist_ver = [p.parts[-2:] for p
+                in Path(distro_root).glob('*/*') if p.is_dir()]
+    return list(dist_ver)
+
+
+def get_components(root):
+    cmpl = [os.path.relpath(dirn, start=root)  # relative names only
+            for dirn, _, _ in os.walk(root)
+            if (os.path.basename(dirn) == 'packaging'  # path ends with
+                and 'contrib' not in Path(dirn).parts)]  # ignore contrib libs
+    return list(cmpl)
+
+
+def test_combinations(distro_vers, components):
+    tests = []
+    for distro, ver in distro_vers:
+        for comp in components:
+            comps = ['scripts/distros', 'daemon/packaging']   # always include daemon
+            if comp not in comps:
+                comps.append(comp)
+            tests.append([distro, ver, *comps])
+    tests.sort()
+    return tests
+
+
+def gen_dockerfile(args, tmpdir, srcdir):
+    subprocess.check_call([GEN_SCRIPT,
+                          '--srcdir={}'.format(srcdir)]  # dir in tar
+                          + args,
+                          stdout=open(tmpdir / 'Dockerfile', 'w'))
+
+
+def docker_build(tmpdir, delete):
+    subprocess.check_call(
+        ['docker',
+         'build',
+         '--rm={}'.format(str(delete).lower()),
+         '--network',
+         'host',
+         tmpdir]
+    )
+
+
+def find_test(required_tests, test_combination):
+    '''
+    Find test in test_combination in required_tests list
+    '''
+    for test in required_tests:
+        if test_combination[len(test_combination)-1] == test[0]:
+            return True
+
+    return False
+
+
+def main():
+    logging.basicConfig(level=logging.DEBUG)
+
+    argparser = argparse.ArgumentParser(
+        formatter_class=argparse.RawTextHelpFormatter,
+        description='''Find all tests in current directory, generate Dockerfiles and run all Dockerfiles one by one.
+'''.format(n=sys.argv[0])
+        )
+    argparser.add_argument(
+        '-t', '--test', action='append', nargs=1, help='Select one test to run')
+    argparser.add_argument(
+        '-l', '--list', action='store_true', help='Show all available tests')
+
+    params = argparser.parse_args()
+
+    distro_vers = get_distro_vers(DISTROS_PATH)
+    components = get_components('.')
+    logging.info('generating fresh tarball')
+    archive = fresh_tarball()
+    logging.debug('generated tarball %s', archive)
+    # transform knot-resolver-1.5.0-70-gf1dbebdc.tar.xz -> knot-resolver-1.5.0-70-gf1dbebdc
+    srcdir = os.path.basename(archive).decode('ascii').rsplit('.', maxsplit=2)[0]
+    logging.debug('expected dir name in tarball: %s', srcdir)
+
+    with tempfile.TemporaryDirectory() as tmpdir:
+        tmpdir = Path(tmpdir)
+        unpack(archive, tmpdir)
+        baseimg = True  # do not delete first image - it works as cache
+        if params.list:
+            print('Available tests: ')
+        # all tests
+        for args in test_combinations(distro_vers, components):
+            if params.list:
+                print('\t' + args[len(args)-1])
+                continue
+            if params.test:
+                if not find_test(params.test, args):
+                    logging.debug('skip test for %s', args)
+                    continue
+
+            logging.debug('generating Dockerfile for %s', args)
+            gen_dockerfile(args, tmpdir, srcdir)
+            logging.info('testing combination %s', args)
+            docker_build(tmpdir, delete=not baseimg)
+            baseimg = False
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/unit/packaging/debian/10/builddeps b/tests/unit/packaging/debian/10/builddeps
new file mode 100644 (file)
index 0000000..5c2068b
--- /dev/null
@@ -0,0 +1 @@
+libcmocka-dev
diff --git a/tests/unit/packaging/test.sh b/tests/unit/packaging/test.sh
new file mode 100755 (executable)
index 0000000..f81d9b9
--- /dev/null
@@ -0,0 +1 @@
+test -e build_packaging/tests/unit/mock_cmodule.so