packaging: add daily feature contract

Why:
Daily packages need a small, tested feature delta while retaining each target distribution's native package definitions.

Impact:
Daily stable base packages require YAML support, and omazuredce is built and published as a separate installable package.

Before:
Native baselines determined the complete feature set, and published-package smoke tests did not verify YAML parsing or optional module ownership.

After:
A shared contract overlays the native Debian, RPM, openSUSE, and Alpine definitions, asserts the module artifact, and smoke-tests exact published package versions.

Technical Overview:
Add a strict packaging overlay, native-family package naming, artifact assertions, and installed-package YAML/module validation to every current daily-stable workflow.

With the help of AI-Agents: Codex
This commit is contained in:
Rainer Gerhards 2026-07-30 10:11:09 +02:00
parent 861dbfc83e
commit 430954bad8
17 changed files with 433 additions and 32 deletions

View File

@ -0,0 +1,30 @@
# Daily stable package feature contract
Daily stable packages start with the target distribution's native rsyslog
packaging. The shared contract in `daily-stable-package-features.yml` adds the
small Adiscon-maintained feature delta without replacing the native package
layout.
The prototype contract requires:
- libyaml-backed YAML configuration support in the base `rsyslog` package;
- a separately installable `omazuredce` module package; and
- published-package smoke tests that parse YAML and load `omazuredce`.
The overlay fails when a native baseline disables YAML, omits the distribution's
YAML development dependency, or no longer contains a unique structural anchor.
This makes native packaging drift a review event instead of silently generating
a different package.
Package names follow native conventions:
| Packaging family | Package |
| --- | --- |
| Debian, Ubuntu | `rsyslog-omazuredce` |
| Fedora, EL, Amazon Linux | `rsyslog-omazuredce` |
| openSUSE | `rsyslog-module-omazuredce` |
| Alpine | `rsyslog-omazuredce` |
The feature smoke test runs only after installing the exact published versions
of both the base and module packages. Scheduled workflow failures continue to
create or update the distribution-specific daily-stable failure issue.

View File

@ -0,0 +1,24 @@
{
"yaml": {
"required_in_base": true,
"build_dependencies": {
"deb": "libyaml-dev",
"rpm": "libyaml-devel",
"apk": "yaml-dev"
}
},
"module_packages": [
{
"module": "omazuredce",
"configure_flag": "--enable-omazuredce",
"module_file": "omazuredce.so",
"summary": "Microsoft Azure Monitor Data Collection Endpoint output module for rsyslog",
"package_names": {
"deb": "rsyslog-omazuredce",
"rpm": "rsyslog-omazuredce",
"opensuse": "rsyslog-module-omazuredce",
"apk": "rsyslog-omazuredce"
}
}
]
}

View File

@ -34,6 +34,7 @@ concurrency:
env:
ALPINE_DAILY_STABLE_HELPER: devtools/release/alpine-daily-stable.sh
ALPINE_POLICY_FILE: .github/alpine324-daily-stable-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
ALPINE_BRANCH: 3.24-stable
ALPINE_ARCH: x86_64
PACKAGE_CHANNEL: daily-stable
@ -167,6 +168,7 @@ jobs:
"$DIST_TARBALL" \
"$RUNNER_TEMP/prepared-aports/main/rsyslog" \
"$ALPINE_POLICY_FILE" \
"$PACKAGE_FEATURE_CONTRACT" \
"$PKGVER"
- name: Validate Alpine archive signing key
@ -505,7 +507,9 @@ jobs:
echo "$ORIGIN_REPO_URL" >> /etc/apk/repositories
fi
if apk update --no-cache &&
apk add --no-cache "rsyslog=$EXPECTED_VERSION"; then
apk add --no-cache \
"rsyslog=$EXPECTED_VERSION" \
"rsyslog-omazuredce=$EXPECTED_VERSION"; then
verification_rc=0
break
fi
@ -519,8 +523,16 @@ jobs:
sed -n 's/.*"version": "\([^"]*\)".*/\1/p'
)"
[ "$installed_version" = "$EXPECTED_VERSION" ]
module_version="$(
apk query --from installed --fields version --format json \
rsyslog-omazuredce |
sed -n 's/.*"version": "\([^"]*\)".*/\1/p'
)"
[ "$module_version" = "$EXPECTED_VERSION" ]
apk info -L rsyslog-omazuredce | \
grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee .ci/flake-evidence/logs/alpine324-published-install.log
install_status=${PIPESTATUS[0]}
set -e

View File

@ -34,6 +34,7 @@ concurrency:
env:
AMAZONLINUX_DAILY_STABLE_HELPER: devtools/release/amazonlinux-daily-stable.sh
AMAZONLINUX_POLICY_FILE: .github/amazonlinux2023-daily-stable-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
RPM_ARCH: x86_64
PACKAGE_CHANNEL: daily-stable
PACKAGE_DISTRO: amazonlinux
@ -160,7 +161,7 @@ jobs:
devtools/ci-flake-phase.sh begin amazonlinux2023-package-build custom
set +e
docker run --rm \
-e AMAZONLINUX_DAILY_STABLE_HELPER \
-e AMAZONLINUX_DAILY_STABLE_HELPER -e PACKAGE_FEATURE_CONTRACT \
-e AMAZONLINUX_POLICY_FILE -e EXPECTED_EVR -e RELEASE -e VERSION \
-e "DIST_TARBALL=/workspace/rsyslog-source/$(basename "$DIST_TARBALL")" \
-v "$GITHUB_WORKSPACE:/workspace" \
@ -194,6 +195,7 @@ jobs:
"$DIST_TARBALL" \
/runner-temp/amazonlinux2023-prepared \
"$AMAZONLINUX_POLICY_FILE" \
"$PACKAGE_FEATURE_CONTRACT" \
"$VERSION" \
"$RELEASE"
"$AMAZONLINUX_DAILY_STABLE_HELPER" build-package \
@ -565,15 +567,18 @@ jobs:
dnf -q -y install \
"rsyslog-$EXPECTED_EVR" \
"rsyslog-logrotate-$EXPECTED_EVR" \
"rsyslog-omazuredce-$EXPECTED_EVR" \
"rsyslog-openssl-$EXPECTED_EVR"
for package in rsyslog rsyslog-logrotate rsyslog-openssl; do
for package in rsyslog rsyslog-logrotate rsyslog-omazuredce \
rsyslog-openssl; do
installed_evr="$(
rpm -q --qf '%{VERSION}-%{RELEASE}\n' "$package"
)"
[ "$installed_evr" = "$EXPECTED_EVR" ]
done
rpm -ql rsyslog-omazuredce | grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee \
.ci/flake-evidence/logs/amazonlinux2023-published-install.log
install_status=${PIPESTATUS[0]}

View File

@ -39,6 +39,7 @@ concurrency:
env:
DEBIAN_CI_POLICY_FILE: .github/debian-daily-stable-13-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
DEBIAN_CI_HELPER: .github/scripts/debian_package_build.sh
DEBIAN_DAILY_STABLE_HELPER: devtools/release/debian-daily-stable.sh
DEBIAN_BUILD_ROOT: /tmp/rsyslog-debian-daily-stable-build
@ -201,6 +202,9 @@ jobs:
apply_install_manifest_additions \
"$DEBIAN_BUILD_ROOT/debian-packaging" \
"$DEBIAN_BUILD_ROOT/debian-ci-policy/install_manifest_additions.tsv"
python3 devtools/release/package-feature-overlay.py debian \
"$DEBIAN_BUILD_ROOT/debian-packaging" \
"$PACKAGE_FEATURE_CONTRACT"
- name: Install Debian build dependencies
run: |
@ -629,14 +633,19 @@ jobs:
echo "deb [signed-by=/usr/share/keyrings/rsyslog-archive-keyring.gpg] $REPO_URL $DEBIAN_SUITE $DEBIAN_COMPONENT" \
> /etc/apt/sources.list.d/rsyslog-daily-stable.list
apt-get update
apt-get install -y "rsyslog=$VERSION"
apt-get install -y \
"rsyslog=$VERSION" \
"rsyslog-omazuredce=$VERSION"
installed_version="$(dpkg-query -W -f='${Version}\n' rsyslog)"
[ "$installed_version" = "$VERSION" ] || {
echo "installed rsyslog version $installed_version does not match $VERSION" >&2
exit 1
}
[ "$(dpkg-query -W -f='${Version}\n' rsyslog-omazuredce)" = "$VERSION" ]
dpkg-query -L rsyslog-omazuredce | \
grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee .ci/flake-evidence/logs/debian-published-install.log
install_status=$?
set -e

View File

@ -34,6 +34,7 @@ concurrency:
env:
RPM_DAILY_STABLE_HELPER: devtools/release/rpm-daily-stable.sh
RPM_POLICY_FILE: .github/el10-daily-stable-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
RPM_MOCK_CONFIG: centos-stream+epel-10-x86_64
RPM_ARCH: x86_64
PACKAGE_CHANNEL: daily-stable
@ -168,7 +169,8 @@ jobs:
set +e
docker run --rm --privileged \
-e EXPECTED_EVR -e RELEASE -e VERSION \
-e RPM_DAILY_STABLE_HELPER -e RPM_MOCK_CONFIG -e RPM_POLICY_FILE \
-e PACKAGE_FEATURE_CONTRACT -e RPM_DAILY_STABLE_HELPER \
-e RPM_MOCK_CONFIG -e RPM_POLICY_FILE \
-e "DIST_TARBALL=/workspace/rsyslog-source/$(basename "$DIST_TARBALL")" \
-v "$GITHUB_WORKSPACE:/workspace" \
-v "$RUNNER_TEMP:/runner-temp" \
@ -183,6 +185,7 @@ jobs:
"$DIST_TARBALL" \
/runner-temp/el10-prepared \
"$RPM_POLICY_FILE" \
"$PACKAGE_FEATURE_CONTRACT" \
"$VERSION" \
"$RELEASE"
"$RPM_DAILY_STABLE_HELPER" build-package \
@ -531,11 +534,16 @@ jobs:
curl -fsSL "$REPO_URL/rsyslog-daily-stable-el10.repo" \
-o /etc/yum.repos.d/rsyslog-daily-stable.repo
dnf -y makecache --refresh
dnf -y install "rsyslog-$EXPECTED_EVR"
dnf -y install \
"rsyslog-$EXPECTED_EVR" \
"rsyslog-omazuredce-$EXPECTED_EVR"
installed_evr="$(rpm -q --qf '%{VERSION}-%{RELEASE}\n' rsyslog)"
[ "$installed_evr" = "$EXPECTED_EVR" ]
[ "$(rpm -q --qf '%{VERSION}-%{RELEASE}\n' rsyslog-omazuredce)" = \
"$EXPECTED_EVR" ]
rpm -ql rsyslog-omazuredce | grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee .ci/flake-evidence/logs/el10-published-install.log
install_status=$?
set -e

View File

@ -34,6 +34,7 @@ concurrency:
env:
FEDORA_DAILY_STABLE_HELPER: devtools/release/fedora-daily-stable.sh
FEDORA_POLICY_FILE: .github/fedora44-daily-stable-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
RPM_ARCH: x86_64
PACKAGE_CHANNEL: daily-stable
PACKAGE_DISTRO: fedora
@ -160,7 +161,7 @@ jobs:
devtools/ci-flake-phase.sh begin fedora44-package-build custom
set +e
docker run --rm \
-e FEDORA_DAILY_STABLE_HELPER \
-e FEDORA_DAILY_STABLE_HELPER -e PACKAGE_FEATURE_CONTRACT \
-e FEDORA_POLICY_FILE -e EXPECTED_EVR -e RELEASE -e VERSION \
-e "DIST_TARBALL=/workspace/rsyslog-source/$(basename "$DIST_TARBALL")" \
-v "$GITHUB_WORKSPACE:/workspace" \
@ -193,6 +194,7 @@ jobs:
"$DIST_TARBALL" \
/runner-temp/fedora44-prepared \
"$FEDORA_POLICY_FILE" \
"$PACKAGE_FEATURE_CONTRACT" \
"$VERSION" \
"$RELEASE"
"$FEDORA_DAILY_STABLE_HELPER" build-package \
@ -564,15 +566,18 @@ jobs:
dnf -q -y install \
"rsyslog-$EXPECTED_EVR" \
"rsyslog-crypto-$EXPECTED_EVR" \
"rsyslog-omazuredce-$EXPECTED_EVR" \
"rsyslog-openssl-$EXPECTED_EVR"
for package in rsyslog rsyslog-crypto rsyslog-openssl; do
for package in rsyslog rsyslog-crypto rsyslog-omazuredce \
rsyslog-openssl; do
installed_evr="$(
rpm -q --qf '%{VERSION}-%{RELEASE}\n' "$package"
)"
[ "$installed_evr" = "$EXPECTED_EVR" ]
done
rpm -ql rsyslog-omazuredce | grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee \
.ci/flake-evidence/logs/fedora44-published-install.log
install_status=${PIPESTATUS[0]}

View File

@ -34,6 +34,7 @@ concurrency:
env:
OPENSUSE_DAILY_STABLE_HELPER: devtools/release/opensuse-daily-stable.sh
OPENSUSE_POLICY_FILE: .github/opensuse-leap160-daily-stable-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
RPM_ARCH: x86_64
PACKAGE_CHANNEL: daily-stable
PACKAGE_DISTRO: opensuse-leap
@ -171,7 +172,7 @@ jobs:
devtools/ci-flake-phase.sh begin opensuse-leap160-package-build custom
set +e
docker run --rm \
-e OPENSUSE_DAILY_STABLE_HELPER \
-e OPENSUSE_DAILY_STABLE_HELPER -e PACKAGE_FEATURE_CONTRACT \
-e OPENSUSE_POLICY_FILE -e EXPECTED_EVR -e RELEASE -e VERSION \
-e "DIST_TARBALL=/workspace/rsyslog-source/$(basename "$DIST_TARBALL")" \
-e "DOC_TARBALL=/workspace/$(basename "$DOC_TARBALL")" \
@ -212,6 +213,7 @@ jobs:
"$DOC_TARBALL" \
/runner-temp/opensuse-leap160-prepared \
"$OPENSUSE_POLICY_FILE" \
"$PACKAGE_FEATURE_CONTRACT" \
"$VERSION" \
"$RELEASE"
"$OPENSUSE_DAILY_STABLE_HELPER" build-package \
@ -584,16 +586,20 @@ jobs:
zypper --gpg-auto-import-keys -n refresh rsyslog-daily-stable
zypper -n install --oldpackage \
"rsyslog=$EXPECTED_EVR" \
"rsyslog-module-omazuredce=$EXPECTED_EVR" \
"rsyslog-module-ossl=$EXPECTED_EVR" \
"rsyslog-doc=$EXPECTED_EVR"
for package in rsyslog rsyslog-module-ossl rsyslog-doc; do
for package in rsyslog rsyslog-module-omazuredce \
rsyslog-module-ossl rsyslog-doc; do
installed_evr="$(
rpm -q --qf '%{VERSION}-%{RELEASE}\n' "$package"
)"
[ "$installed_evr" = "$EXPECTED_EVR" ]
done
rpm -ql rsyslog-module-omazuredce | \
grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee \
.ci/flake-evidence/logs/opensuse-leap160-published-install.log
install_status=${PIPESTATUS[0]}

View File

@ -39,6 +39,7 @@ concurrency:
env:
DEBIAN_CI_POLICY_FILE: .github/ubuntu-daily-stable-26.04-policy.yml
PACKAGE_FEATURE_CONTRACT: .github/daily-stable-package-features.yml
DEBIAN_CI_HELPER: .github/scripts/debian_package_build.sh
DEBIAN_DAILY_STABLE_HELPER: devtools/release/debian-daily-stable.sh
DEBIAN_BUILD_ROOT: /tmp/rsyslog-ubuntu-daily-stable-build
@ -202,6 +203,9 @@ jobs:
apply_install_manifest_additions \
"$DEBIAN_BUILD_ROOT/debian-packaging" \
"$DEBIAN_BUILD_ROOT/debian-ci-policy/install_manifest_additions.tsv"
python3 devtools/release/package-feature-overlay.py debian \
"$DEBIAN_BUILD_ROOT/debian-packaging" \
"$PACKAGE_FEATURE_CONTRACT"
- name: Install Ubuntu build dependencies
run: |
@ -630,14 +634,19 @@ jobs:
echo "deb [signed-by=/usr/share/keyrings/rsyslog-archive-keyring.gpg] $REPO_URL $DEBIAN_SUITE $DEBIAN_COMPONENT" \
> /etc/apt/sources.list.d/rsyslog-daily-stable.list
apt-get update
apt-get install -y "rsyslog=$VERSION"
apt-get install -y \
"rsyslog=$VERSION" \
"rsyslog-omazuredce=$VERSION"
installed_version="$(dpkg-query -W -f='${Version}\n' rsyslog)"
[ "$installed_version" = "$VERSION" ] || {
echo "installed rsyslog version $installed_version does not match $VERSION" >&2
exit 1
}
[ "$(dpkg-query -W -f='${Version}\n' rsyslog-omazuredce)" = "$VERSION" ]
dpkg-query -L rsyslog-omazuredce | \
grep -Eq '/rsyslog/omazuredce\.so$'
rsyslogd -v
rsyslogd -N1
devtools/release/package-feature-smoke.sh
) 2>&1 | tee .ci/flake-evidence/logs/ubuntu-published-install.log
install_status=$?
set -e

View File

@ -7,7 +7,7 @@ Usage: alpine-daily-stable.sh <command> [args...]
Commands:
version
prepare-sources <baseline-dir> <dist-tarball> <output-dir> <policy-file> <pkgver>
prepare-sources <baseline-dir> <dist-tarball> <output-dir> <policy-file> <feature-contract> <pkgver>
verify-artifacts <artifact-dir> <expected-version> <arch>
generate-index <artifact-dir> <previous-index> <repo-dir> <arch> <private-key> <public-key>
manifest <artifact-dir> <expected-version> <arch> <channel> <distro> <distro-version>
@ -63,13 +63,15 @@ cmd_prepare_sources() {
local dist_tarball="$2"
local output_dir="$3"
local policy_file="$4"
local pkgver="$5"
local feature_contract="$5"
local pkgver="$6"
local tmp_dir source_root
[ -f "$baseline_dir/APKBUILD" ] ||
die "missing Alpine packaging baseline: $baseline_dir/APKBUILD"
[ -f "$dist_tarball" ] || die "missing dist tarball: $dist_tarball"
[ -f "$policy_file" ] || die "missing Alpine packaging policy: $policy_file"
[ -f "$feature_contract" ] || die "missing package feature contract: $feature_contract"
rm -rf "$output_dir"
mkdir -p "$output_dir"
@ -129,6 +131,9 @@ if checksum_count != 1:
path.write_text(text.rstrip() + "\n", encoding="utf-8")
PY
python3 "$(dirname "$0")/package-feature-overlay.py" alpine \
"$output_dir/APKBUILD" "$feature_contract"
rm -rf "$tmp_dir"
trap - RETURN
}
@ -143,6 +148,8 @@ cmd_verify_artifacts() {
-name "rsyslog-${expected_version}.apk" -print -quit)"
[ -n "$base_apk" ] ||
die "base rsyslog APK for $expected_version is absent"
[ -f "$artifact_dir/rsyslog-omazuredce-$expected_version.apk" ] ||
die "omazuredce subpackage APK for $expected_version is absent"
if ! apk verify "$base_apk"; then
die "base rsyslog APK failed signature or integrity verification"
fi

View File

@ -7,7 +7,7 @@ Usage: amazonlinux-daily-stable.sh <command> [args...]
Commands:
version
prepare-sources <baseline-root> <dist-tarball> <output-dir> <policy-file> <version> <release>
prepare-sources <baseline-root> <dist-tarball> <output-dir> <policy-file> <feature-contract> <version> <release>
build-package <prepared-dir> <artifact-dir> <build-log> <expected-evr> <arch>
sign-rpms <artifact-dir> <fingerprint>
generate-repo <artifact-dir> <repo-dir> <arch> <fingerprint> <passphrase-file>
@ -75,8 +75,9 @@ cmd_prepare_sources() {
local dist_tarball="$2"
local output_dir="$3"
local policy_file="$4"
local version="$5"
local release="$6"
local feature_contract="$5"
local version="$6"
local release="$7"
local baseline_spec baseline_sources spec_file tmp_dir source_root
baseline_spec="$baseline_root/SPECS/rsyslog.spec"
@ -85,6 +86,7 @@ cmd_prepare_sources() {
[ -d "$baseline_sources" ] || die "missing Amazon Linux sources: $baseline_sources"
[ -f "$dist_tarball" ] || die "missing dist tarball: $dist_tarball"
[ -f "$policy_file" ] || die "missing Amazon Linux policy: $policy_file"
[ -f "$feature_contract" ] || die "missing package feature contract: $feature_contract"
rm -rf "$output_dir"
mkdir -p "$output_dir/SOURCES" "$output_dir/SPECS"
@ -257,6 +259,9 @@ if changelog_count != 1:
spec_path.write_text(spec, encoding="utf-8")
PY
python3 "$(dirname "$0")/package-feature-overlay.py" rpm \
"$spec_file" "$feature_contract" rpm
rm -rf "$tmp_dir"
trap - RETURN
}
@ -307,6 +312,9 @@ cmd_build_package() {
actual_evr="$(rpm -qp --qf '%{VERSION}-%{RELEASE}\n' "$rpm_file")"
[ "$actual_evr" = "$expected_evr" ] ||
die "built RPM EVR $actual_evr does not match $expected_evr"
find "$artifact_dir/rpms" -maxdepth 1 -type f \
-name 'rsyslog*omazuredce-[0-9]*.rpm' -print -quit | grep -q . ||
die "omazuredce subpackage RPM was not produced"
cp "$build_log" "$artifact_dir/build.log"
cp "$prepared_dir/SPECS/rsyslog.spec" "$artifact_dir/rsyslog.spec"

View File

@ -156,6 +156,9 @@ cmd_build_package() {
find "$artifact_dir" -maxdepth 1 -type f -name '*.deb' | grep -q . ||
die "no .deb artifacts collected"
find "$artifact_dir" -maxdepth 1 -type f \
-name 'rsyslog-omazuredce_*.deb' -print -quit | grep -q . ||
die "omazuredce subpackage DEB was not produced"
find "$artifact_dir" -maxdepth 1 -type f -name '*.dsc' | grep -q . ||
die "no .dsc artifact collected"
find "$artifact_dir" -maxdepth 1 -type f -name '*.changes' | grep -q . ||

View File

@ -7,7 +7,7 @@ Usage: fedora-daily-stable.sh <command> [args...]
Commands:
version
prepare-sources <baseline-root> <dist-tarball> <output-dir> <policy-file> <version> <release>
prepare-sources <baseline-root> <dist-tarball> <output-dir> <policy-file> <feature-contract> <version> <release>
build-package <prepared-dir> <artifact-dir> <build-log> <expected-evr> <arch>
sign-rpms <artifact-dir> <fingerprint>
generate-repo <artifact-dir> <repo-dir> <arch> <fingerprint> <passphrase-file>
@ -75,8 +75,9 @@ cmd_prepare_sources() {
local dist_tarball="$2"
local output_dir="$3"
local policy_file="$4"
local version="$5"
local release="$6"
local feature_contract="$5"
local version="$6"
local release="$7"
local baseline_spec baseline_sources spec_file tmp_dir source_root
baseline_spec="$baseline_root/SPECS/rsyslog.spec"
@ -85,6 +86,7 @@ cmd_prepare_sources() {
[ -d "$baseline_sources" ] || die "missing Fedora sources: $baseline_sources"
[ -f "$dist_tarball" ] || die "missing dist tarball: $dist_tarball"
[ -f "$policy_file" ] || die "missing Fedora policy: $policy_file"
[ -f "$feature_contract" ] || die "missing package feature contract: $feature_contract"
rm -rf "$output_dir"
mkdir -p "$output_dir/SOURCES" "$output_dir/SPECS"
@ -159,6 +161,9 @@ if changelog_count != 1:
spec_path.write_text(spec, encoding="utf-8")
PY
python3 "$(dirname "$0")/package-feature-overlay.py" rpm \
"$spec_file" "$feature_contract" rpm
rm -rf "$tmp_dir"
trap - RETURN
}
@ -209,6 +214,9 @@ cmd_build_package() {
actual_evr="$(rpm -qp --qf '%{VERSION}-%{RELEASE}\n' "$rpm_file")"
[ "$actual_evr" = "$expected_evr" ] ||
die "built RPM EVR $actual_evr does not match $expected_evr"
find "$artifact_dir/rpms" -maxdepth 1 -type f \
-name 'rsyslog*omazuredce-[0-9]*.rpm' -print -quit | grep -q . ||
die "omazuredce subpackage RPM was not produced"
cp "$build_log" "$artifact_dir/build.log"
cp "$prepared_dir/SPECS/rsyslog.spec" "$artifact_dir/rsyslog.spec"

View File

@ -7,7 +7,7 @@ Usage: opensuse-daily-stable.sh <command> [args...]
Commands:
version
prepare-sources <baseline-root> <dist-tarball> <doc-tarball> <output-dir> <policy-file> <version> <release>
prepare-sources <baseline-root> <dist-tarball> <doc-tarball> <output-dir> <policy-file> <feature-contract> <version> <release>
build-package <prepared-dir> <artifact-dir> <build-log> <expected-evr> <arch>
sign-rpms <artifact-dir> <fingerprint>
generate-repo <artifact-dir> <repo-dir> <arch> <fingerprint> <passphrase-file>
@ -76,8 +76,9 @@ cmd_prepare_sources() {
local doc_tarball="$3"
local output_dir="$4"
local policy_file="$5"
local version="$6"
local release="$7"
local feature_contract="$6"
local version="$7"
local release="$8"
local baseline_spec baseline_sources spec_file tmp_dir source_root
baseline_spec="$baseline_root/SPECS/rsyslog.spec"
@ -87,6 +88,7 @@ cmd_prepare_sources() {
[ -f "$dist_tarball" ] || die "missing dist tarball: $dist_tarball"
[ -f "$doc_tarball" ] || die "missing documentation tarball: $doc_tarball"
[ -f "$policy_file" ] || die "missing openSUSE policy: $policy_file"
[ -f "$feature_contract" ] || die "missing package feature contract: $feature_contract"
rm -rf "$output_dir"
mkdir -p "$output_dir/SOURCES" "$output_dir/SPECS"
@ -186,6 +188,9 @@ if changelog_count != 1:
spec_path.write_text(spec, encoding="utf-8")
PY
python3 "$(dirname "$0")/package-feature-overlay.py" rpm \
"$spec_file" "$feature_contract" opensuse
rm -rf "$tmp_dir"
trap - RETURN
}
@ -234,6 +239,9 @@ cmd_build_package() {
actual_evr="$(rpm -qp --qf '%{VERSION}-%{RELEASE}\n' "$rpm_file")"
[ "$actual_evr" = "$expected_evr" ] ||
die "built RPM EVR $actual_evr does not match $expected_evr"
find "$artifact_dir/rpms" -maxdepth 1 -type f \
-name 'rsyslog*omazuredce-[0-9]*.rpm' -print -quit | grep -q . ||
die "omazuredce subpackage RPM was not produced"
cp "$build_log" "$artifact_dir/build.log"
cp "$prepared_dir/SPECS/rsyslog.spec" "$artifact_dir/rsyslog.spec"

View File

@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""Apply the daily-stable feature contract to native distro packaging."""
import argparse
import json
import pathlib
import re
import sys
def fail(message):
raise SystemExit(message)
def replace_once(text, pattern, replacement, label):
updated, count = re.subn(pattern, replacement, text, count=1)
if count != 1:
fail(f"could not find unique {label}")
return updated
def load_contract(path):
contract = json.loads(pathlib.Path(path).read_text(encoding="utf-8"))
yaml_feature = contract.get("yaml", {})
if yaml_feature.get("required_in_base") is not True:
fail("feature contract must require YAML support in the base package")
modules = contract.get("module_packages", [])
if len(modules) != 1 or modules[0].get("module") != "omazuredce":
fail("prototype contract must define exactly the omazuredce module package")
return contract, yaml_feature, modules[0]
def require_dependency(text, dependency, source_name):
if not re.search(rf"(?m)(?:^|\s){re.escape(dependency)}(?:\s|$|,)", text):
fail(f"{source_name} does not declare required YAML dependency {dependency}")
def apply_debian(packaging_dir, contract_path):
root = pathlib.Path(packaging_dir)
contract, yaml_feature, module = load_contract(contract_path)
del contract
control_path = root / "control"
rules_path = root / "rules"
if not control_path.is_file() or not rules_path.is_file():
fail(f"missing Debian control or rules file below {root}")
control = control_path.read_text(encoding="utf-8")
rules = rules_path.read_text(encoding="utf-8")
require_dependency(control, yaml_feature["build_dependencies"]["deb"], "Debian control")
if "--disable-libyaml" in rules:
fail("Debian rules explicitly disable required YAML support")
package_name = module["package_names"]["deb"]
if not re.search(rf"(?m)^Package: {re.escape(package_name)}$", control):
stanza = (
f"\nPackage: {package_name}\n"
"Architecture: any\n"
"Depends: ${shlibs:Depends},\n"
" ${misc:Depends},\n"
" rsyslog (= ${binary:Version})\n"
f"Description: {module['summary']}\n"
" This package provides the omazuredce output module for sending logs to\n"
" Microsoft Azure Monitor through a Data Collection Endpoint.\n"
)
control = control.rstrip() + "\n" + stanza
configure_flag = module["configure_flag"]
if configure_flag not in rules:
rules = replace_once(
rules,
r"(?m)^(\s*)(--enable-omprog\s+\\)$",
rf"\g<1>{configure_flag} \\" + "\n" + r"\g<1>\g<2>",
"Debian configure option anchor",
)
manifest = root / f"{package_name}.install"
expected_path = f"usr/lib/${{DEB_HOST_MULTIARCH}}/rsyslog/{module['module_file']}"
if manifest.exists():
entries = manifest.read_text(encoding="utf-8").splitlines()
if expected_path not in entries:
fail(f"existing {manifest.name} does not own {expected_path}")
else:
manifest.write_text(expected_path + "\n", encoding="utf-8")
control_path.write_text(control, encoding="utf-8")
rules_path.write_text(rules, encoding="utf-8")
def rpm_package_block(module, flavor):
package_name = module["package_names"][flavor]
suffix = package_name.removeprefix("rsyslog-")
if flavor == "opensuse":
return (
f"%package {suffix}\n"
"Requires: %{name} = %{version}\n"
f"Summary: {module['summary']}\n"
"Group: System/Daemons\n\n"
f"%description {suffix}\n"
"Rsyslog is an enhanced multi-threaded syslog daemon. See rsyslog\n"
"package.\n\n"
"This module sends logs to Microsoft Azure Monitor through a Data\n"
"Collection Endpoint.\n\n"
)
return (
f"%package {suffix}\n"
f"Summary: {module['summary']}\n"
"Requires: %name = %version-%release\n\n"
f"%description {suffix}\n"
"This module sends logs to Microsoft Azure Monitor through a Data\n"
"Collection Endpoint.\n\n"
)
def apply_rpm(spec_path, contract_path, flavor):
path = pathlib.Path(spec_path)
contract, yaml_feature, module = load_contract(contract_path)
del contract
text = path.read_text(encoding="utf-8")
require_dependency(text, yaml_feature["build_dependencies"]["rpm"], "RPM spec")
if "--disable-libyaml" in text:
fail("RPM spec explicitly disables required YAML support")
package_name = module["package_names"][flavor]
suffix = package_name.removeprefix("rsyslog-")
if not re.search(rf"(?m)^%package\s+{re.escape(suffix)}$", text):
text = replace_once(text, r"(?m)^%prep\s*$", rpm_package_block(module, flavor) + "%prep", "RPM %prep")
configure_flag = module["configure_flag"]
if configure_flag not in text:
text = replace_once(
text,
r"(?m)^(\s*--enable-omhttp(?:fs)?\s*\\)$",
rf"\t{configure_flag} \\" + "\n" + r"\g<1>",
"RPM configure option anchor",
)
if flavor == "opensuse" and module["module_file"] not in text:
text = replace_once(
text,
r"(?m)^(\s*omhttpfs\.so\s*\\)$",
rf"\1\n\t\t{module['module_file']} \\",
"openSUSE module relocation anchor",
)
files_header = f"%files {suffix}"
module_macro = "%{rsyslog_module_dir_withdeps}" if flavor == "opensuse" else "%{_libdir}/rsyslog"
if not re.search(rf"(?m)^{re.escape(files_header)}$", text):
files = f"{files_header}\n{module_macro}/{module['module_file']}\n\n"
text = replace_once(text, r"(?m)^%changelog\s*$", files + "%changelog", "RPM %changelog")
path.write_text(text, encoding="utf-8")
def apply_alpine(apkbuild_path, contract_path):
path = pathlib.Path(apkbuild_path)
contract, yaml_feature, module = load_contract(contract_path)
del contract
text = path.read_text(encoding="utf-8")
require_dependency(text, yaml_feature["build_dependencies"]["apk"], "APKBUILD")
if "--disable-libyaml" in text:
fail("APKBUILD explicitly disables required YAML support")
package_name = module["package_names"]["apk"]
plugin_name = package_name.removeprefix("rsyslog-")
plugins_match = re.search(r'(?ms)^_plugins="\n(?P<body>.*?)\n\s*"$', text)
if not plugins_match:
fail("could not find Alpine _plugins block")
plugin_entries = [line.strip() for line in plugins_match.group("body").splitlines()]
if plugin_name not in plugin_entries:
text = replace_once(
text,
r"(?m)^(\s*omotel\s*)$",
rf"\1\n\t{plugin_name}",
"Alpine plugin package anchor",
)
configure_flag = module["configure_flag"]
if configure_flag not in text:
text = replace_once(
text,
r"(?m)^(\s*--enable-omotel\s*\\)$",
rf"\1\n\t\t{configure_flag} \\",
"Alpine configure option anchor",
)
path.write_text(text, encoding="utf-8")
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command", required=True)
debian = subparsers.add_parser("debian")
debian.add_argument("packaging_dir")
debian.add_argument("contract")
rpm = subparsers.add_parser("rpm")
rpm.add_argument("spec")
rpm.add_argument("contract")
rpm.add_argument("flavor", choices=("rpm", "opensuse"))
alpine = subparsers.add_parser("alpine")
alpine.add_argument("apkbuild")
alpine.add_argument("contract")
args = parser.parse_args()
if args.command == "debian":
apply_debian(args.packaging_dir, args.contract)
elif args.command == "rpm":
apply_rpm(args.spec, args.contract, args.flavor)
elif args.command == "alpine":
apply_alpine(args.apkbuild, args.contract)
if __name__ == "__main__":
try:
main()
except (KeyError, json.JSONDecodeError) as error:
print(f"invalid package feature contract: {error}", file=sys.stderr)
raise SystemExit(1) from error

View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Verify the package contract after installing the base and module packages.
set -euo pipefail
rsyslogd_bin="${RSYSLOGD_BIN:-rsyslogd}"
module_args=()
if [ -n "${RSYSLOG_MODULE_PATH:-}" ]; then
module_args=(-M "$RSYSLOG_MODULE_PATH")
fi
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
cat > "$tmp_dir/base.yaml" <<'EOF'
version: 2
global:
preserveFQDN: "off"
rulesets:
- name: main
script: |
action(type="omfile" file="/dev/null")
EOF
cat > "$tmp_dir/omazuredce.yaml" <<'EOF'
version: 2
modules:
- load: omazuredce
rulesets:
- name: main
script: |
action(type="omfile" file="/dev/null")
EOF
"$rsyslogd_bin" -N1 -f "$tmp_dir/base.yaml" "${module_args[@]}"
"$rsyslogd_bin" -N1 -f "$tmp_dir/omazuredce.yaml" "${module_args[@]}"

View File

@ -7,7 +7,7 @@ Usage: rpm-daily-stable.sh <command> [args...]
Commands:
version
prepare-sources <baseline-dir> <dist-tarball> <output-dir> <policy-file> <version> <release>
prepare-sources <baseline-dir> <dist-tarball> <output-dir> <policy-file> <feature-contract> <version> <release>
build-package <prepared-dir> <mock-config> <artifact-dir> <build-log> <expected-evr>
sign-rpms <artifact-dir> <fingerprint>
generate-repo <artifact-dir> <repo-dir> <arch> <fingerprint> <passphrase-file>
@ -75,14 +75,16 @@ cmd_prepare_sources() {
local dist_tarball="$2"
local output_dir="$3"
local policy_file="$4"
local version="$5"
local release="$6"
local feature_contract="$5"
local version="$6"
local release="$7"
local sources_dir specs_dir spec_file tmp_dir source_root
[ -f "$baseline_dir/rsyslog.spec" ] ||
die "missing EL packaging spec: $baseline_dir/rsyslog.spec"
[ -f "$dist_tarball" ] || die "missing dist tarball: $dist_tarball"
[ -f "$policy_file" ] || die "missing RPM policy: $policy_file"
[ -f "$feature_contract" ] || die "missing package feature contract: $feature_contract"
rm -rf "$output_dir"
sources_dir="$output_dir/SOURCES"
@ -184,6 +186,9 @@ if changelog_count != 1:
spec_path.write_text(spec, encoding="utf-8")
PY
python3 "$(dirname "$0")/package-feature-overlay.py" rpm \
"$spec_file" "$feature_contract" rpm
while IFS= read -r source_url; do
case "$source_url" in
http://*|https://*)
@ -236,6 +241,9 @@ cmd_build_package() {
actual_evr="$(rpm -qp --qf '%{VERSION}-%{RELEASE}\n' "$rpm_file")"
[ "$actual_evr" = "$expected_evr" ] ||
die "built RPM EVR $actual_evr does not match $expected_evr"
find "$artifact_dir/rpms" -maxdepth 1 -type f \
-name 'rsyslog*omazuredce-[0-9]*.rpm' -print -quit | grep -q . ||
die "omazuredce subpackage RPM was not produced"
cp "$build_log" "$artifact_dir/build.log"
cp "$prepared_dir/SPECS/rsyslog.spec" "$artifact_dir/rsyslog.spec"