Skip to content

Commit

Permalink
Add disable-units and enable-units hooks
Browse files Browse the repository at this point in the history
Add the hook scripts `disable-units` and `enable-units` for disabling
and enabling services / systemd units.

Signed-off-by: Benjamin Drung <[email protected]>
  • Loading branch information
bdrung committed Dec 6, 2021
1 parent 8a6ad4a commit 8399b50
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 6 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
apt-get update
apt-get install --yes black isort pylint python3 python3-coverage python3-flake8 python3-ruamel.yaml
run: >
apt-get update &&
apt-get install --yes black isort pylint python3 python3-coverage
python3-flake8 python3-ruamel.yaml shellcheck
- name: Run unit tests
run: |
python3 -m coverage run -m unittest discover -v
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,34 @@ the virtual machine is started, it can be accessed via SSH:
$ ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -p 2222 root@localhost
```

Hooks
=====

bdebstrap ships a bunch of hook scripts that can be used to configure the
produced chroot. The environment variable `BDEBSTRAP_HOOKS` can be used to
refer to the path to the hook script. Available hooks:

* disable-units
* enable-units

Disable/enable services
-----------------------

The `disable-units` and `enable-units` hook script can be used to
disable/enable services. Either service names or full systemd unit names should
be specified. Example:

```yaml
---
mmdebstrap:
customize-hooks:
- $BDEBSTRAP_HOOKS/disable-units "$1" apt-daily.timer cron
- $BDEBSTRAP_HOOKS/enable-units "$1" systemd-timesyncd
suite: bullseye
target: root.tar
variant: important
```
Prerequisites
=============
Expand All @@ -144,6 +172,7 @@ The test cases have additional Python module requirements:
* flake8
* isort
* pylint
* shellcheck
Thanks
======
Expand Down
6 changes: 6 additions & 0 deletions bdebstrap
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ import time

import ruamel.yaml

HOOKS_DIR = (
"/usr/share/bdebstrap/hooks"
if os.path.dirname(__file__) == "/usr/bin"
else os.path.join(os.path.dirname(__file__), "hooks")
)
MANIFEST_FILENAME = "manifest"
OUTPUT_DIR = "/tmp/bdebstrap-output"
LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s: %(message)s"
Expand Down Expand Up @@ -589,6 +594,7 @@ class Config(dict):
return sorted(
list(self.get("env", {}).items())
+ [
(self._ENV_PREFIX + "HOOKS", HOOKS_DIR),
(self._ENV_PREFIX + "NAME", self["name"]),
(self._ENV_PREFIX + "OUTPUT_DIR", OUTPUT_DIR),
]
Expand Down
24 changes: 24 additions & 0 deletions bdebstrap.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ environment variables specified via the *env* configuration option or the
**\--env** parameter. **bdebstrap** sets following environment variables by
default to be consumed by the hooks:

**BDEBSTRAP_HOOKS**
: Path to the hooks provided by bdebstrap.

**BDEBSTRAP_NAME**
: name of the generated golden image which is set via the **name**
configuration option of the **\--name** parameter.
Expand All @@ -304,6 +307,27 @@ default to be consumed by the hooks:
the output directory. This temporary directory will be removed in a final
cleanup hook.

Following hook scripts are shipped with **bdebstrap**:

**disable-units**
: Disable services / systemd units.

**enable-units**
: Enable services / systemd units.

Example usage for the hook scripts:

```yaml
---
mmdebstrap:
customize-hooks:
- $BDEBSTRAP_HOOKS/disable-units "$1" apt-daily.timer cron
- $BDEBSTRAP_HOOKS/enable-units "$1" systemd-timesyncd
suite: bullseye
target: root.tar
variant: important
```
# EXAMPLES
### Minimal Debian unstable tarball
Expand Down
31 changes: 31 additions & 0 deletions hooks/disable-units
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
set -eu

# Copyright (C) 2021, Benjamin Drung <[email protected]>
# SPDX-License-Identifier: ISC

# Disable services / systemd units in a chroot environment.

if test "$#" = 0; then
echo "${0##*/}: Called without required CHROOT_DIR argument." >&2
echo "usage: ${0##*/} CHROOT_DIR service/systemd_unit [service/system_unit...]" >&2
exit 1
fi

chroot_directory="$1"
shift

for unit in "$@"; do
unit_files=$(test ! -d "${chroot_directory}/etc/systemd/system" ||
find "${chroot_directory}"/etc/systemd/system/*.wants -name "$unit" -o -name "${unit}.service")
if test -n "$unit_files"; then
echo "${0##*/}: Disabling unit $unit..." >&2
rm "$unit_files"
elif test -e "${chroot_directory}/etc/init.d/$unit"; then
echo "${0##*/}: Disabling SysV init.d service $unit..." >&2
echo "${0##*/}: Calling chroot \"${chroot_directory}\" /usr/sbin/update-rc.d \"$unit\" disable" >&2
chroot "${chroot_directory}" /usr/sbin/update-rc.d "$unit" disable
else
echo "${0##*/}: Unit $unit not found. Skipping disabling it." >&2
fi
done
32 changes: 32 additions & 0 deletions hooks/enable-units
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh
set -eu

# Copyright (C) 2021, Benjamin Drung <[email protected]>
# SPDX-License-Identifier: ISC

# Enable services / systemd units in a chroot environment.

if test "$#" = 0; then
echo "${0##*/}: Called without required CHROOT_DIR argument." >&2
echo "usage: ${0##*/} CHROOT_DIR service/systemd_unit [service/system_unit...]" >&2
exit 1
fi

chroot_directory="$1"
shift

for unit in "$@"; do
if test -e "${chroot_directory}/etc/systemd/system/${unit}" || \
test -e "${chroot_directory}/lib/systemd/system/${unit}" || \
test -e "${chroot_directory}/usr/lib/systemd/system/${unit}" || \
test -e "${chroot_directory}/etc/systemd/system/${unit}.service" || \
test -e "${chroot_directory}/lib/systemd/system/${unit}.service" || \
test -e "${chroot_directory}/usr/lib/systemd/system/${unit}.service" || \
test -e "${chroot_directory}/etc/init.d/$unit"; then
echo "${0##*/}: Enabling unit $unit..." >&2
echo "${0##*/}: Calling chroot \"${chroot_directory}\" /bin/systemctl enable \"$unit\"" >&2
chroot "${chroot_directory}" /bin/systemctl enable "$unit"
else
echo "${0##*/}: Unit $unit not found. Skipping enabling it." >&2
fi
done
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from setuptools import setup

HOOKS = ["hooks/disable-units", "hooks/enable-units"]
MAN_PAGES = ["bdebstrap.1"]


Expand Down Expand Up @@ -97,5 +98,5 @@ def run(self):
install_requires=["ruamel.yaml"],
scripts=["bdebstrap"],
py_modules=[],
data_files=[("/usr/share/man/man1", MAN_PAGES)],
data_files=[("/usr/share/man/man1", MAN_PAGES), ("/usr/share/bdebstrap/hooks", HOOKS)],
)
2 changes: 1 addition & 1 deletion tests/pylint.conf
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ reports=no
[SIMILARITIES]

# Minimum lines number of a similarity.
min-similarity-lines=7
min-similarity-lines=10


[FORMAT]
Expand Down
3 changes: 2 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import unittest
import unittest.mock

from bdebstrap import Config, dict_merge, parse_args
from bdebstrap import HOOKS_DIR, Config, dict_merge, parse_args

EXAMPLE_CONFIG_DIR = os.path.join(os.path.dirname(__file__), "..", "examples")
TEST_CONFIG_DIR = os.path.join(os.path.dirname(__file__), "configs")
Expand Down Expand Up @@ -365,6 +365,7 @@ def test_env_items(self):
self.assertEqual(
config.env_items(),
[
("BDEBSTRAP_HOOKS", HOOKS_DIR),
("BDEBSTRAP_NAME", "Debian-unstable"),
("BDEBSTRAP_OUTPUT_DIR", "/tmp/bdebstrap-output"),
],
Expand Down
56 changes: 56 additions & 0 deletions tests/test_shellcheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (C) 2021, Benjamin Drung <[email protected]>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

"""Run shellcheck on Shell code."""

import subprocess
import sys
import unittest

from . import get_path, unittest_verbosity

SHELL_SCRIPTS = ["hooks/disable-units", "hooks/enable-units"]


class ShellcheckTestCase(unittest.TestCase):
"""
This unittest class provides a test that runs the shellcheck
on Shell source code.
"""

def test_flake8(self):
"""Test: Run shellcheck on Shell source code."""
cmd = ["shellcheck"] + [get_path(s) for s in SHELL_SCRIPTS]
if unittest_verbosity() >= 2:
sys.stderr.write(f"Running following command:\n{' '.join(cmd)}\n")
with subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True
) as process:
out, err = process.communicate()

if process.returncode != 0: # pragma: no cover
msgs = []
if err:
msgs.append(
f"shellcheck exited with code {process.returncode} "
f"and has unexpected output on stderr:\n{err.decode().rstrip()}"
)
if out:
msgs.append(f"shellcheck found issues:\n{out.decode().rstrip()}")
if not msgs:
msgs.append(
f"shellcheck exited with code {process.returncode} "
"and has no output on stdout or stderr."
)
self.fail("\n".join(msgs))

0 comments on commit 8399b50

Please sign in to comment.