Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/package-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,15 @@ jobs:

# The package manager enforces the engine/corpus pairing (spec
# release-upgrade C-06). AC-09 is verified in Go CI against the resolvers;
# this runs the same scenarios in real containers, scriptlets included. A Kensa engine older than its corpus
# this runs the same scenarios in real containers, with scriptlets and, on
# RPM, again under tsflags=noscripts. A Kensa engine older than its corpus
# cannot load it and the service starts anyway with every scan failing, so
# a rules-only upgrade beside an older openwatch must be refused while the
# coordinated upgrade, an openwatch-only upgrade and a fresh install
# succeed. The previous GA predates the engine provide, which is the state
# the refusal has to hold against.
kensa-rules-compat:
name: kensa-rules compat ${{ matrix.distro }}
name: kensa-rules compat ${{ matrix.distro }}${{ matrix.mode && format(' {0}', matrix.mode) || '' }}
needs: build
runs-on: ubuntu-latest
strategy:
Expand All @@ -230,6 +231,10 @@ jobs:
include:
- { distro: 'rockylinux:9', kind: rpm }
- { distro: 'ubuntu:24.04', kind: deb }
# The same scenarios with every RPM transaction under
# tsflags=noscripts, so the refusals rest on dependency
# resolution alone. A pass with scriptlets is not evidence for it.
- { distro: 'rockylinux:9', kind: rpm, mode: noscripts }
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v4
Expand All @@ -256,6 +261,7 @@ jobs:
env:
OPENWATCH_KENSA_COMPAT_IMAGE: ${{ matrix.distro }}
OPENWATCH_KENSA_COMPAT_KIND: ${{ matrix.kind }}
OPENWATCH_KENSA_COMPAT_MODE: ${{ matrix.mode }}
run: |
OPENWATCH_KENSA_COMPAT_OLD_DIR="$PWD/old" OPENWATCH_KENSA_COMPAT_NEW_DIR="$PWD/new" \
go test -count=1 -v -run 'TestUpgrade_EngineCorpusPairingInContainers' ./packaging/tests/
Expand Down
60 changes: 45 additions & 15 deletions packaging/tests/kensa-rules-compat-container-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,41 @@
# should be installed. A failed `dpkg -i` can leave the recorded version
# looking unchanged while the files are already replaced.
#
# Usage: kensa-rules-compat-container-test.sh <rpm|deb> <old-dir> <new-dir>
# Usage: kensa-rules-compat-container-test.sh <rpm|deb> <old-dir> <new-dir> [scripts|noscripts]
# old-dir: an openwatch release that predates the engine provide, and its
# kensa-rules (package-smoke passes the previous GA).
# new-dir: openwatch and kensa-rules built from this tree.
#
# Scriptlets run on both formats. The openwatch scriptlets tolerate a
# container without systemd or a database: the upgrade scriptlet skips its
# migration when `openwatch migrate --status` cannot connect.
# mode: scripts (the default) runs scriptlets on both formats. The
# openwatch scriptlets tolerate a container without systemd or a
# database: the upgrade scriptlet skips its migration when
# `openwatch migrate --status` cannot connect.
# noscripts (RPM only) runs every transaction with
# tsflags=noscripts (dnf) or --noscripts (rpm), so the refusals
# rest on dependency resolution alone. Every scenario and check
# is the same, except that the scriptlet check is inverted: it
# asserts no identity keys were provisioned, which proves the
# mode took effect. A pass in one mode is not evidence for the
# other.

set -uo pipefail

KIND="${1:?usage: $0 <rpm|deb> <old-dir> <new-dir>}"
OLD="${2:?old-dir}"
NEW="${3:?new-dir}"
MODE="${4:-scripts}"

# Options every RPM transaction takes in this mode.
DNF_OPTS=()
RPM_OPTS=()
case "$MODE" in
scripts) ;;
noscripts)
[ "$KIND" = rpm ] || { echo "noscripts mode is RPM only" >&2; exit 2; }
DNF_OPTS=(--setopt=tsflags=noscripts)
RPM_OPTS=(--noscripts)
;;
*) echo "unknown mode: $MODE" >&2; exit 2 ;;
esac

FAILURES=0
pass() { echo "PASS: $*"; }
Expand Down Expand Up @@ -87,7 +108,7 @@ txn() {
if [ "$KIND" = deb ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades "$@" >"$log" 2>&1
else
dnf install -y "$@" >"$log" 2>&1
dnf install -y "${DNF_OPTS[@]}" "$@" >"$log" 2>&1
fi
local rc=$?
LAST_LOG="$log"
Expand Down Expand Up @@ -199,7 +220,7 @@ fi
check_state "after the refused rules-only upgrade" "$V_OLD_OW" "$V_OLD_KR" old
if [ "$KIND" = rpm ]; then
# The plain rpm path checks dependencies too; --nodeps is the only bypass.
if rpm -U "$NEW_KR" >/tmp/rpmU.log 2>&1; then
if rpm -U "${RPM_OPTS[@]}" "$NEW_KR" >/tmp/rpmU.log 2>&1; then
fail "rpm -U installed the new corpus beside the older openwatch"
else
pass "rpm -U refused: $(grep -m1 openwatch-kensa-engine /tmp/rpmU.log | sed 's/^[[:space:]]*//')"
Expand All @@ -225,8 +246,15 @@ else
fi
check_state "after the coordinated upgrade" "$V_NEW_OW" "$V_NEW_KR" new
# Scriptlets ran: the openwatch pre-install creates the service user, and the
# post-install provisions the identity keys.
if getent passwd openwatch >/dev/null && [ -n "$(ls -A /etc/openwatch/keys 2>/dev/null)" ]; then
# post-install provisions the identity keys. Under noscripts neither may have
# happened; the keys are the check, since only %post creates them.
if [ "$MODE" = noscripts ]; then
if [ -z "$(ls -A /etc/openwatch/keys 2>/dev/null)" ]; then
pass "scriptlets did not run: no identity keys are provisioned"
else
fail "identity keys exist, so a scriptlet ran despite noscripts"
fi
elif getent passwd openwatch >/dev/null && [ -n "$(ls -A /etc/openwatch/keys 2>/dev/null)" ]; then
pass "scriptlets ran: the openwatch user exists and identity keys are provisioned"
else
fail "scriptlets did not run: no openwatch user or no identity keys"
Expand All @@ -235,7 +263,7 @@ fi
if [ "$KIND" = rpm ]; then
echo "### 3b. dnf upgrade of both packages succeeds"
reset_to_old
if dnf upgrade -y "$NEW_OW" "$NEW_KR" >/tmp/dnfup.log 2>&1; then
if dnf upgrade -y "${DNF_OPTS[@]}" "$NEW_OW" "$NEW_KR" >/tmp/dnfup.log 2>&1; then
pass "dnf upgrade accepted"
else
fail "dnf upgrade failed; log follows"; cat /tmp/dnfup.log
Expand All @@ -244,7 +272,7 @@ if [ "$KIND" = rpm ]; then

echo "### 3c. a joint rpm -Uvh of both packages succeeds"
reset_to_old
if rpm -Uvh "$NEW_OW" "$NEW_KR" >/tmp/rpmUvh.log 2>&1; then
if rpm -Uvh "${RPM_OPTS[@]}" "$NEW_OW" "$NEW_KR" >/tmp/rpmUvh.log 2>&1; then
pass "joint rpm -Uvh accepted"
else
fail "joint rpm -Uvh failed; log follows"; cat /tmp/rpmUvh.log
Expand Down Expand Up @@ -283,9 +311,9 @@ check_state "kensa-rules refused, then openwatch, then kensa-rules" "$V_NEW_OW"

if [ "$KIND" = rpm ]; then
reset_to_old
if rpm -U "$NEW_OW" >/tmp/rpmU1.log 2>&1; then pass "rpm -U openwatch first: accepted"; else fail "rpm -U openwatch first failed"; cat /tmp/rpmU1.log; fi
if rpm -U "${RPM_OPTS[@]}" "$NEW_OW" >/tmp/rpmU1.log 2>&1; then pass "rpm -U openwatch first: accepted"; else fail "rpm -U openwatch first failed"; cat /tmp/rpmU1.log; fi
check_state "rpm -U openwatch first" "$V_NEW_OW" "$V_OLD_KR" old
if rpm -U "$NEW_KR" >/tmp/rpmU2.log 2>&1; then pass "then rpm -U kensa-rules: accepted"; else fail "then rpm -U kensa-rules failed"; cat /tmp/rpmU2.log; fi
if rpm -U "${RPM_OPTS[@]}" "$NEW_KR" >/tmp/rpmU2.log 2>&1; then pass "then rpm -U kensa-rules: accepted"; else fail "then rpm -U kensa-rules failed"; cat /tmp/rpmU2.log; fi
check_state "rpm -U openwatch, then kensa-rules" "$V_NEW_OW" "$V_NEW_KR" new
fi

Expand Down Expand Up @@ -348,10 +376,12 @@ for pair in "1:0.8.0~rc.5 1:0.8.0~rc.6" "1:0.8.0~rc.6 1:0.8.0~rc.10" "1:0.8.0~rc
older_than "$1" "$2" && pass "$1 < $2" || fail "$1 does not sort before $2"
done

LABEL="$KIND"
[ "$MODE" = scripts ] || LABEL="$KIND $MODE"
echo
if [ "$FAILURES" -eq 0 ]; then
echo "kensa-rules compat ($KIND): all checks passed"
echo "kensa-rules compat ($LABEL): all checks passed"
exit 0
fi
echo "kensa-rules compat ($KIND): $FAILURES check(s) failed"
echo "kensa-rules compat ($LABEL): $FAILURES check(s) failed"
exit 1
7 changes: 4 additions & 3 deletions packaging/tests/run-kensa-rules-compat-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# run-kensa-rules-compat-test.sh: host-side runner for
# kensa-rules-compat-container-test.sh (spec release-upgrade AC-09).
#
# Usage: run-kensa-rules-compat-test.sh <image> <rpm|deb> <old-dir> <new-dir>
# e.g. run-kensa-rules-compat-test.sh rockylinux:9 rpm /tmp/old dist
# Usage: run-kensa-rules-compat-test.sh <image> <rpm|deb> <old-dir> <new-dir> [scripts|noscripts]
# e.g. run-kensa-rules-compat-test.sh rockylinux:9 rpm /tmp/old dist noscripts
# old-dir holds a release that predates the engine provide (the published
# v0.8.0-rc.5 openwatch and kensa-rules assets); new-dir holds the packages
# built from this tree. Needs docker.
Expand All @@ -12,11 +12,12 @@ IMAGE="${1:?usage: $0 <image> <rpm|deb> <old-dir> <new-dir>}"
KIND="${2:?kind}"
OLD="$(cd "${3:?old-dir}" && pwd)"
NEW="$(cd "${4:?new-dir}" && pwd)"
MODE="${5:-scripts}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
command -v docker >/dev/null || { echo "docker is required" >&2; exit 1; }

docker run --rm \
-v "$OLD":/pk/old:ro \
-v "$NEW":/pk/new:ro \
-v "$SCRIPT_DIR/kensa-rules-compat-container-test.sh":/t.sh:ro \
"$IMAGE" bash /t.sh "$KIND" /pk/old /pk/new
"$IMAGE" bash /t.sh "$KIND" /pk/old /pk/new "$MODE"
15 changes: 12 additions & 3 deletions packaging/tests/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ func TestUpgrade_PackagesDeclareEngineCorpusPairing(t *testing.T) {
// engine_pairing_test.go, which runs in Go CI. package-smoke's
// kensa-rules-compat job sets the four variables (previous GA as the old
// release, the candidate's packages as the new); elsewhere this skips.
// OPENWATCH_KENSA_COMPAT_MODE=noscripts runs every RPM transaction without
// scriptlets; unset, scriptlets run.
func TestUpgrade_EngineCorpusPairingInContainers(t *testing.T) {
t.Run("containers", func(t *testing.T) {
image := os.Getenv("OPENWATCH_KENSA_COMPAT_IMAGE")
Expand All @@ -215,14 +217,21 @@ func TestUpgrade_EngineCorpusPairingInContainers(t *testing.T) {
if image == "" || kind == "" || oldDir == "" || newDir == "" {
t.Skip("set OPENWATCH_KENSA_COMPAT_{IMAGE,KIND,OLD_DIR,NEW_DIR} to run the container pairing test")
}
mode := os.Getenv("OPENWATCH_KENSA_COMPAT_MODE")
label := kind
if mode == "" {
mode = "scripts"
} else if mode != "scripts" {
label = kind + " " + mode
}
haveTool(t, "docker")
runner := filepath.Join(appDir(t), "packaging", "tests", "run-kensa-rules-compat-test.sh")
out, err := exec.Command("bash", runner, image, kind, oldDir, newDir).CombinedOutput()
out, err := exec.Command("bash", runner, image, kind, oldDir, newDir, mode).CombinedOutput()
t.Logf("%s", out)
if err != nil {
t.Fatalf("pairing test failed on %s (%s): %v", image, kind, err)
t.Fatalf("pairing test failed on %s (%s): %v", image, label, err)
}
if !strings.Contains(string(out), "kensa-rules compat ("+kind+"): all checks passed") {
if !strings.Contains(string(out), "kensa-rules compat ("+label+"): all checks passed") {
t.Fatal("the container test did not report a complete pass")
}
})
Expand Down
Loading