mirror of
https://github.com/rsyslog/rsyslog.git
synced 2026-08-24 19:24:14 +02:00
packaging: add EL10 daily stable RPM archive
Why: Provide unattended current-main packages for the newest Enterprise Linux generation without maintaining duplicate repositories for each clone. Impact: Adds a disabled-by-default daily EL10 build, publish, and install flow. Before/After: Before, the archive covered Debian and Ubuntu; after, it also covers EL10. Technical Overview: Use the current CentOS Stream 10 spec as the distro packaging baseline. Build current rsyslog main with Mock for the EL10 x86_64 target. Apply explicit policy for upstreamed patches and new build requirements. Sign RPMs and repository metadata with the existing archive key. Publish clone-neutral x86_64 and SRPM repositories to DigitalOcean Spaces. Merge prior metadata so every immutable daily package version remains usable. Verify exact signed installs on CentOS Stream, Rocky, Alma, and Oracle Linux. Create or update a GitHub issue when an enabled scheduled run fails. With the help of AI-Agents: Codex
This commit is contained in:
parent
b2faae551a
commit
020b4dc2f7
30
.github/el10-daily-stable-policy.yml
vendored
Normal file
30
.github/el10-daily-stable-policy.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"allowed_patch_skips": [
|
||||
{
|
||||
"patch": "imfile-inotify-fd-release-on-delete.patch",
|
||||
"reason": "The EL10 deferred-delete fix is already represented by newer imfile lifecycle handling in current upstream."
|
||||
},
|
||||
{
|
||||
"patch": "imjournal-warn-on-missing-MESSAGE-field.patch",
|
||||
"reason": "The EL10 missing MESSAGE warning patch is already present in current upstream."
|
||||
},
|
||||
{
|
||||
"patch": "omelasticsearch-pqc-tls.patch",
|
||||
"reason": "The EL10 Elasticsearch TLS controls are already present in current upstream with later development."
|
||||
},
|
||||
{
|
||||
"patch": "omelasticsearch-apply-tls-opts-during-detection.patch",
|
||||
"reason": "The EL10 Elasticsearch detection TLS fix is already present in current upstream with later development."
|
||||
}
|
||||
],
|
||||
"supplemental_build_requires": [
|
||||
{
|
||||
"package": "autoconf-archive",
|
||||
"reason": "Current upstream configure.ac uses AX macros while the EL10 8.2604 source package does not declare the archive explicitly."
|
||||
},
|
||||
{
|
||||
"package": "libyaml-devel",
|
||||
"reason": "Current upstream enables YAML configuration support by default while the EL10 8.2604 source package predates that build dependency."
|
||||
}
|
||||
]
|
||||
}
|
||||
1
.github/workflows/collect_flake_evidence.yml
vendored
1
.github/workflows/collect_flake_evidence.yml
vendored
@ -15,6 +15,7 @@ name: collect flake evidence
|
||||
- impstats push to VictoriaMetrics
|
||||
- debian daily stable
|
||||
- ubuntu daily stable
|
||||
- el10 daily stable
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
603
.github/workflows/el10_daily_stable.yml
vendored
Normal file
603
.github/workflows/el10_daily_stable.yml
vendored
Normal file
@ -0,0 +1,603 @@
|
||||
# Copyright 2026 Rainer Gerhards and Others
|
||||
#
|
||||
# https://github.com/rsyslog/rsyslog
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
---
|
||||
name: el10 daily stable
|
||||
|
||||
'on':
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source_ref:
|
||||
description: rsyslog source branch, tag, or commit to package
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
publish_to_archive:
|
||||
description: Publish to the configured DigitalOcean Spaces archive
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
schedule:
|
||||
- cron: '43 4 * * *'
|
||||
|
||||
concurrency:
|
||||
group: el10-daily-stable
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
RPM_DAILY_STABLE_HELPER: devtools/release/rpm-daily-stable.sh
|
||||
RPM_POLICY_FILE: .github/el10-daily-stable-policy.yml
|
||||
RPM_MOCK_CONFIG: centos-stream+epel-10-x86_64
|
||||
RPM_ARCH: x86_64
|
||||
PACKAGE_CHANNEL: daily-stable
|
||||
PACKAGE_DISTRO: el
|
||||
PACKAGE_DISTRO_VERSION: '10'
|
||||
SPACE_PREFIX: rpm/daily-stable/el/10
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
name: preflight
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.repository == 'rsyslog/rsyslog'
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
should_run: ${{ steps.decision.outputs.should_run }}
|
||||
should_publish: ${{ steps.decision.outputs.should_publish }}
|
||||
source_ref: ${{ steps.decision.outputs.source_ref }}
|
||||
steps:
|
||||
- name: Decide whether this run is active
|
||||
id: decision
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
MANUAL_PUBLISH: ${{ inputs.publish_to_archive }}
|
||||
MANUAL_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
SCHEDULE_ENABLED: ${{ vars.EL10_DAILY_STABLE_ENABLED }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
should_run=false
|
||||
should_publish=false
|
||||
source_ref=main
|
||||
case "$EVENT_NAME" in
|
||||
workflow_dispatch)
|
||||
should_run=true
|
||||
source_ref="${MANUAL_SOURCE_REF:-main}"
|
||||
if [ "${MANUAL_PUBLISH:-false}" = true ]; then
|
||||
should_publish=true
|
||||
fi
|
||||
;;
|
||||
schedule)
|
||||
if [ "${SCHEDULE_ENABLED:-false}" = true ]; then
|
||||
should_run=true
|
||||
should_publish=true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
{
|
||||
echo "should_run=$should_run"
|
||||
echo "should_publish=$should_publish"
|
||||
echo "source_ref=$source_ref"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: build EL10 x86_64 packages
|
||||
needs: preflight
|
||||
if: needs.preflight.outputs.should_run == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 150
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
archive_date: ${{ steps.version.outputs.archive_date }}
|
||||
expected_evr: ${{ steps.version.outputs.expected_evr }}
|
||||
source_sha: ${{ steps.source.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout archive automation
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout source to package
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
path: rsyslog-source
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.preflight.outputs.source_ref }}
|
||||
|
||||
- name: Record packaged source revision
|
||||
id: source
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source_sha="$(git -C rsyslog-source rev-parse HEAD)"
|
||||
{
|
||||
echo "source_sha=$source_sha"
|
||||
echo "SOURCE_GIT_SHA=$source_sha"
|
||||
echo "RSYSLOG_SOURCE_DIR=$GITHUB_WORKSPACE/rsyslog-source"
|
||||
} | tee -a "$GITHUB_ENV" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate daily package version
|
||||
id: version
|
||||
run: "$RPM_DAILY_STABLE_HELPER version"
|
||||
|
||||
- name: Fetch official CentOS Stream 10 packaging baseline
|
||||
id: packaging
|
||||
run: |
|
||||
git clone --depth 1 --branch c10s \
|
||||
https://gitlab.com/redhat/centos-stream/rpms/rsyslog.git \
|
||||
"$RUNNER_TEMP/el10-packaging"
|
||||
baseline_sha="$(git -C "$RUNNER_TEMP/el10-packaging" rev-parse HEAD)"
|
||||
echo "baseline_sha=$baseline_sha" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate rsyslog dist tarball
|
||||
run: |
|
||||
RSYSLOG_HOME="$GITHUB_WORKSPACE/rsyslog-source" \
|
||||
RSYSLOG_DEV_CONTAINER=rsyslog/rsyslog_dev_base_ubuntu:26.04 \
|
||||
devtools/devcontainer.sh --rm \
|
||||
.github/scripts/debian_package_build.sh run_dist_build \
|
||||
/rsyslog
|
||||
dist_tarball="$(find rsyslog-source -maxdepth 1 -type f -name 'rsyslog-*.tar.gz' -print -quit)"
|
||||
[ -n "$dist_tarball" ]
|
||||
echo "DIST_TARBALL=$GITHUB_WORKSPACE/$dist_tarball" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Prepare EL10 sources and run Mock build
|
||||
id: package_build
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
RELEASE: ${{ steps.version.outputs.release }}
|
||||
EXPECTED_EVR: ${{ steps.version.outputs.expected_evr }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$RUNNER_TEMP/el10-build"
|
||||
devtools/ci-flake-phase.sh begin el10-package-build custom
|
||||
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 "DIST_TARBALL=/workspace/rsyslog-source/$(basename "$DIST_TARBALL")" \
|
||||
-v "$GITHUB_WORKSPACE:/workspace" \
|
||||
-v "$RUNNER_TEMP:/runner-temp" \
|
||||
-w /workspace \
|
||||
quay.io/rockylinux/rockylinux:10 bash -lc '
|
||||
set -euo pipefail
|
||||
dnf -q -y install epel-release >/dev/null
|
||||
dnf -q -y install \
|
||||
curl git mock mock-core-configs python3 rpm-build tar gzip >/dev/null
|
||||
"$RPM_DAILY_STABLE_HELPER" prepare-sources \
|
||||
/runner-temp/el10-packaging \
|
||||
"$DIST_TARBALL" \
|
||||
/runner-temp/el10-prepared \
|
||||
"$RPM_POLICY_FILE" \
|
||||
"$VERSION" \
|
||||
"$RELEASE"
|
||||
"$RPM_DAILY_STABLE_HELPER" build-package \
|
||||
/runner-temp/el10-prepared \
|
||||
"$RPM_MOCK_CONFIG" \
|
||||
/runner-temp/el10-artifacts \
|
||||
/runner-temp/el10-build.log \
|
||||
"$EXPECTED_EVR"
|
||||
'
|
||||
build_status=$?
|
||||
set -e
|
||||
devtools/ci-flake-phase.sh end \
|
||||
el10-package-build custom "$build_status"
|
||||
if [ "$build_status" -eq 0 ]; then
|
||||
sudo chown -R "$(id -u):$(id -g)" \
|
||||
"$RUNNER_TEMP/el10-artifacts" "$RUNNER_TEMP/el10-build.log"
|
||||
cp -a "$RUNNER_TEMP/el10-artifacts" el10-daily-stable-artifacts
|
||||
fi
|
||||
exit "$build_status"
|
||||
|
||||
- name: Upload package-build failure evidence
|
||||
if: failure() && steps.package_build.outcome == 'failure'
|
||||
uses: ./.github/actions/upload-flake-evidence
|
||||
with:
|
||||
job-name: EL10 daily stable package build
|
||||
|
||||
- name: Generate artifact manifest
|
||||
env:
|
||||
EXPECTED_EVR: ${{ steps.version.outputs.expected_evr }}
|
||||
run: |
|
||||
"$RPM_DAILY_STABLE_HELPER" manifest \
|
||||
el10-daily-stable-artifacts \
|
||||
"$EXPECTED_EVR" \
|
||||
"$RPM_ARCH" \
|
||||
"$PACKAGE_CHANNEL" \
|
||||
"$PACKAGE_DISTRO" \
|
||||
"$PACKAGE_DISTRO_VERSION"
|
||||
|
||||
- name: Upload EL10 package artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: el10-daily-stable-${{ steps.version.outputs.expected_evr }}
|
||||
path: el10-daily-stable-artifacts/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Summarize build
|
||||
env:
|
||||
BASELINE_SHA: ${{ steps.packaging.outputs.baseline_sha }}
|
||||
EXPECTED_EVR: ${{ steps.version.outputs.expected_evr }}
|
||||
SOURCE_REF: ${{ needs.preflight.outputs.source_ref }}
|
||||
run: |
|
||||
{
|
||||
echo '### Enterprise Linux 10 daily stable build'
|
||||
echo
|
||||
echo "- EVR: \`$EXPECTED_EVR\`"
|
||||
echo "- Source ref: \`$SOURCE_REF\`"
|
||||
echo "- Source commit: \`$SOURCE_GIT_SHA\`"
|
||||
echo "- CentOS Stream 10 packaging baseline: \`$BASELINE_SHA\`"
|
||||
echo "- Mock target: \`$RPM_MOCK_CONFIG\`"
|
||||
echo "- Archive prefix: \`$SPACE_PREFIX\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
publish:
|
||||
name: publish DigitalOcean Spaces RPM archive
|
||||
needs: [preflight, build]
|
||||
if: needs.preflight.outputs.should_publish == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
# This legacy-named environment is the security boundary for the shared
|
||||
# packages.rsyslog.com archive, not a Debian-only destination. Reusing it
|
||||
# deliberately keeps every package family on the same Spaces bucket and
|
||||
# archive signing key without duplicating long-lived credentials.
|
||||
environment: debian-daily-stable
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
# The DEBIAN_* names are retained for compatibility with the existing
|
||||
# shared archive environment; the values are distribution-neutral.
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.DEBIAN_DAILY_STABLE_SPACE_ACCESS_KEY }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEBIAN_DAILY_STABLE_SPACE_SECRET_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ vars.DEBIAN_DAILY_STABLE_SPACE_REGION }}
|
||||
SPACE_BUCKET: ${{ vars.DEBIAN_DAILY_STABLE_SPACE_BUCKET }}
|
||||
SPACE_ENDPOINT: ${{ vars.DEBIAN_DAILY_STABLE_SPACE_ENDPOINT }}
|
||||
REPO_URL: ${{ vars.EL10_DAILY_STABLE_REPO_URL }}
|
||||
ARCHIVE_GPG_PRIVATE_KEY: ${{ secrets.DEBIAN_DAILY_STABLE_GPG_PRIVATE_KEY }}
|
||||
ARCHIVE_GPG_PASSPHRASE: ${{ secrets.DEBIAN_DAILY_STABLE_GPG_PASSPHRASE }}
|
||||
ARCHIVE_GPG_FINGERPRINT: ${{ vars.DEBIAN_DAILY_STABLE_GPG_FINGERPRINT }}
|
||||
steps:
|
||||
- name: Checkout archive automation
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install RPM repository tools
|
||||
run: |
|
||||
command -v aws
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
createrepo-c gnupg rpm
|
||||
|
||||
- name: Validate archive configuration
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for variable in \
|
||||
AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION \
|
||||
SPACE_BUCKET SPACE_ENDPOINT REPO_URL ARCHIVE_GPG_PRIVATE_KEY \
|
||||
ARCHIVE_GPG_FINGERPRINT; do
|
||||
[ -n "${!variable:-}" ] || {
|
||||
echo "$variable is empty" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
- name: Download EL10 package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: el10-daily-stable-${{ needs.build.outputs.expected_evr }}
|
||||
path: el10-daily-stable-artifacts
|
||||
|
||||
- name: Verify artifact checksums
|
||||
run: |
|
||||
cd el10-daily-stable-artifacts
|
||||
sha256sum -c SHA256SUMS
|
||||
|
||||
- name: Import signing key and unlock its agent
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install -m 700 -d "$HOME/.gnupg"
|
||||
echo allow-preset-passphrase > "$HOME/.gnupg/gpg-agent.conf"
|
||||
gpgconf --kill gpg-agent || true
|
||||
printf '%s\n' "$ARCHIVE_GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
keygrip="$(
|
||||
gpg --batch --with-colons --with-keygrip --list-secret-keys \
|
||||
"$ARCHIVE_GPG_FINGERPRINT" |
|
||||
awk -F: '$1 == "grp" { print $10; exit }'
|
||||
)"
|
||||
[ -n "$keygrip" ]
|
||||
preset_tool="$(command -v gpg-preset-passphrase || true)"
|
||||
for candidate in \
|
||||
/usr/lib/gnupg2/gpg-preset-passphrase \
|
||||
/usr/lib/gnupg/gpg-preset-passphrase; do
|
||||
if [ -z "$preset_tool" ] && [ -x "$candidate" ]; then
|
||||
preset_tool="$candidate"
|
||||
fi
|
||||
done
|
||||
[ -n "$preset_tool" ]
|
||||
printf '%s' "$ARCHIVE_GPG_PASSPHRASE" |
|
||||
"$preset_tool" --preset "$keygrip"
|
||||
passphrase_file="$RUNNER_TEMP/archive-gpg-passphrase"
|
||||
install -m 600 /dev/null "$passphrase_file"
|
||||
printf '%s' "$ARCHIVE_GPG_PASSPHRASE" > "$passphrase_file"
|
||||
echo "GPG_PASSPHRASE_FILE=$passphrase_file" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Sign binary and source RPMs
|
||||
run: |
|
||||
"$RPM_DAILY_STABLE_HELPER" sign-rpms \
|
||||
el10-daily-stable-artifacts \
|
||||
"$ARCHIVE_GPG_FINGERPRINT"
|
||||
|
||||
- name: Download current repository metadata
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "rpm-repo/$RPM_ARCH" rpm-repo/SRPMS
|
||||
for repo_subdir in "$RPM_ARCH" SRPMS; do
|
||||
aws s3 sync \
|
||||
--only-show-errors \
|
||||
--endpoint-url "$SPACE_ENDPOINT" \
|
||||
"s3://$SPACE_BUCKET/$SPACE_PREFIX/$repo_subdir/repodata" \
|
||||
"rpm-repo/$repo_subdir/repodata"
|
||||
done
|
||||
|
||||
- name: Generate signed incremental RPM repositories
|
||||
run: |
|
||||
"$RPM_DAILY_STABLE_HELPER" generate-repo \
|
||||
el10-daily-stable-artifacts \
|
||||
rpm-repo \
|
||||
"$RPM_ARCH" \
|
||||
"$ARCHIVE_GPG_FINGERPRINT" \
|
||||
"$GPG_PASSPHRASE_FILE"
|
||||
|
||||
- name: Generate signed artifact manifest and snapshot
|
||||
env:
|
||||
ARCHIVE_DATE: ${{ needs.build.outputs.archive_date }}
|
||||
EXPECTED_EVR: ${{ needs.build.outputs.expected_evr }}
|
||||
run: |
|
||||
"$RPM_DAILY_STABLE_HELPER" manifest \
|
||||
el10-daily-stable-artifacts \
|
||||
"$EXPECTED_EVR" "$RPM_ARCH" "$PACKAGE_CHANNEL" \
|
||||
"$PACKAGE_DISTRO" "$PACKAGE_DISTRO_VERSION"
|
||||
snapshot_dir="rpm-repo/snapshots/$ARCHIVE_DATE/$EXPECTED_EVR"
|
||||
mkdir -p "$snapshot_dir"
|
||||
cp -a el10-daily-stable-artifacts/manifest.json \
|
||||
el10-daily-stable-artifacts/SHA256SUMS \
|
||||
el10-daily-stable-artifacts/build.log \
|
||||
el10-daily-stable-artifacts/rsyslog.spec \
|
||||
"$snapshot_dir/"
|
||||
|
||||
- name: Generate repository configuration
|
||||
run: |
|
||||
cat > rpm-repo/rsyslog-daily-stable-el10.repo <<EOF
|
||||
[rsyslog-daily-stable]
|
||||
name=rsyslog daily stable for Enterprise Linux 10 - \$basearch
|
||||
baseurl=$REPO_URL/\$basearch
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1
|
||||
gpgkey=$REPO_URL/rsyslog-archive-keyring.asc
|
||||
EOF
|
||||
|
||||
- name: Publish immutable RPMs and snapshots
|
||||
run: |
|
||||
set -euo pipefail
|
||||
upload_immutable() {
|
||||
local path="$1"
|
||||
local relative_path="${path#rpm-repo/}"
|
||||
local key="$SPACE_PREFIX/$relative_path"
|
||||
local local_hash remote_hash remote_key
|
||||
remote_key="$(
|
||||
aws s3api list-objects-v2 \
|
||||
--endpoint-url "$SPACE_ENDPOINT" --bucket "$SPACE_BUCKET" \
|
||||
--prefix "$key" --max-keys 1 \
|
||||
--query 'Contents[0].Key' --output text
|
||||
)"
|
||||
local_hash="$(sha256sum "$path" | awk '{print $1}')"
|
||||
if [ "$remote_key" = "$key" ]; then
|
||||
remote_hash="$(
|
||||
aws s3 cp --quiet --endpoint-url "$SPACE_ENDPOINT" \
|
||||
"s3://$SPACE_BUCKET/$key" - | sha256sum | awk '{print $1}'
|
||||
)"
|
||||
[ "$remote_hash" = "$local_hash" ] || {
|
||||
echo "immutable archive collision at $key" >&2
|
||||
exit 1
|
||||
}
|
||||
return
|
||||
fi
|
||||
aws s3 cp --only-show-errors --endpoint-url "$SPACE_ENDPOINT" \
|
||||
--acl public-read \
|
||||
--cache-control 'public,max-age=31536000,immutable' \
|
||||
--metadata "sha256=$local_hash" \
|
||||
"$path" "s3://$SPACE_BUCKET/$key"
|
||||
}
|
||||
while IFS= read -r -d '' path; do
|
||||
upload_immutable "$path"
|
||||
done < <(
|
||||
find "rpm-repo/$RPM_ARCH/Packages" rpm-repo/SRPMS/Packages \
|
||||
rpm-repo/snapshots -type f -print0
|
||||
)
|
||||
|
||||
- name: Publish archive key and mutable metadata
|
||||
run: |
|
||||
set -euo pipefail
|
||||
upload_metadata() {
|
||||
local path="$1"
|
||||
local relative_path="${path#rpm-repo/}"
|
||||
aws s3 cp --only-show-errors --endpoint-url "$SPACE_ENDPOINT" \
|
||||
--acl public-read \
|
||||
--cache-control 'public,max-age=60,must-revalidate' \
|
||||
"$path" "s3://$SPACE_BUCKET/$SPACE_PREFIX/$relative_path"
|
||||
}
|
||||
upload_metadata rpm-repo/rsyslog-archive-keyring.asc
|
||||
upload_metadata rpm-repo/rsyslog-daily-stable-el10.repo
|
||||
for repo_subdir in "$RPM_ARCH" SRPMS; do
|
||||
while IFS= read -r -d '' path; do
|
||||
upload_metadata "$path"
|
||||
done < <(
|
||||
find "rpm-repo/$repo_subdir/repodata" -type f \
|
||||
! -name repomd.xml ! -name repomd.xml.asc -print0
|
||||
)
|
||||
upload_metadata "rpm-repo/$repo_subdir/repodata/repomd.xml"
|
||||
upload_metadata "rpm-repo/$repo_subdir/repodata/repomd.xml.asc"
|
||||
done
|
||||
|
||||
verify:
|
||||
name: verify ${{ matrix.name }} EL10 repository
|
||||
needs: [preflight, build, publish]
|
||||
if: needs.preflight.outputs.should_publish == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: CentOS Stream
|
||||
image: quay.io/centos/centos:stream10
|
||||
- name: Rocky Linux
|
||||
image: quay.io/rockylinux/rockylinux:10
|
||||
- name: AlmaLinux
|
||||
image: almalinux:10
|
||||
- name: Oracle Linux
|
||||
image: container-registry.oracle.com/os/oraclelinux:10
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
options: --user root
|
||||
environment: debian-daily-stable
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
REPO_URL: ${{ vars.EL10_DAILY_STABLE_REPO_URL }}
|
||||
EXPECTED_GPG_FINGERPRINT: ${{ vars.DEBIAN_DAILY_STABLE_GPG_FINGERPRINT }}
|
||||
EXPECTED_EVR: ${{ needs.build.outputs.expected_evr }}
|
||||
steps:
|
||||
- name: Checkout archive automation
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install verification tools
|
||||
run: dnf -y install ca-certificates curl gnupg2 rpm
|
||||
|
||||
- name: Wait for CDN and verify signed metadata
|
||||
id: published_metadata
|
||||
run: |
|
||||
set -euo pipefail
|
||||
devtools/ci-flake-phase.sh begin el10-published-metadata custom
|
||||
set +e
|
||||
verification_rc=1
|
||||
for attempt in $(seq 1 20); do
|
||||
if "$RPM_DAILY_STABLE_HELPER" verify-repo \
|
||||
"$REPO_URL" "$RPM_ARCH" "$EXPECTED_EVR" \
|
||||
"$EXPECTED_GPG_FINGERPRINT"; then
|
||||
verification_rc=0
|
||||
break
|
||||
fi
|
||||
echo "Repository not ready yet, retrying ($attempt/20)..."
|
||||
[ "$attempt" -eq 20 ] || sleep 30
|
||||
done
|
||||
set -e
|
||||
devtools/ci-flake-phase.sh end \
|
||||
el10-published-metadata custom "$verification_rc"
|
||||
exit "$verification_rc"
|
||||
|
||||
- name: Install and smoke-test the exact published RPM
|
||||
id: published_install
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .ci/flake-evidence/logs
|
||||
devtools/ci-flake-phase.sh begin el10-published-install custom
|
||||
set +e
|
||||
(
|
||||
set -euo pipefail
|
||||
curl -fsSL "$REPO_URL/rsyslog-archive-keyring.asc" \
|
||||
-o /tmp/rsyslog-archive-keyring.asc
|
||||
rpm --import /tmp/rsyslog-archive-keyring.asc
|
||||
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"
|
||||
installed_evr="$(rpm -q --qf '%{VERSION}-%{RELEASE}\n' rsyslog)"
|
||||
[ "$installed_evr" = "$EXPECTED_EVR" ]
|
||||
rsyslogd -v
|
||||
rsyslogd -N1
|
||||
) 2>&1 | tee .ci/flake-evidence/logs/el10-published-install.log
|
||||
install_status=$?
|
||||
set -e
|
||||
devtools/ci-flake-phase.sh end \
|
||||
el10-published-install custom "$install_status"
|
||||
exit "$install_status"
|
||||
|
||||
- name: Upload publication failure evidence
|
||||
if: >-
|
||||
${{
|
||||
failure() &&
|
||||
(steps.published_metadata.outcome == 'failure' ||
|
||||
steps.published_install.outcome == 'failure')
|
||||
}}
|
||||
uses: ./.github/actions/upload-flake-evidence
|
||||
with:
|
||||
job-name: ${{ matrix.name }} EL10 daily stable verification
|
||||
|
||||
report_failure:
|
||||
name: report failure
|
||||
needs: [preflight, build, publish, verify]
|
||||
if: >-
|
||||
always() &&
|
||||
github.event_name == 'schedule' &&
|
||||
(contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled'))
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Create or update failure issue
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
VERSION: ${{ needs.build.outputs.expected_evr }}
|
||||
PREFLIGHT_RESULT: ${{ needs.preflight.result }}
|
||||
BUILD_RESULT: ${{ needs.build.result }}
|
||||
PUBLISH_RESULT: ${{ needs.publish.result }}
|
||||
VERIFY_RESULT: ${{ needs.verify.result }}
|
||||
with:
|
||||
script: |
|
||||
const version = process.env.VERSION || `run-${context.runId}`;
|
||||
const title = '[el10-daily-stable] package archive failure';
|
||||
const body = [
|
||||
`Automated EL10 daily stable failed for \`${version}\`.`,
|
||||
'',
|
||||
`Workflow run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
|
||||
`Workflow commit: ${context.sha}`,
|
||||
'',
|
||||
'Job results:',
|
||||
`- preflight: ${process.env.PREFLIGHT_RESULT}`,
|
||||
`- build: ${process.env.BUILD_RESULT}`,
|
||||
`- publish: ${process.env.PUBLISH_RESULT}`,
|
||||
`- verify: ${process.env.VERIFY_RESULT}`
|
||||
].join('\n');
|
||||
const {owner, repo} = context.repo;
|
||||
const existing = await github.paginate(github.rest.issues.listForRepo, {
|
||||
owner, repo, state: 'open', per_page: 100
|
||||
});
|
||||
const issue = existing.find(item => item.title === title && !item.pull_request);
|
||||
if (issue) {
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: issue.number, body
|
||||
});
|
||||
return;
|
||||
}
|
||||
const created = await github.rest.issues.create({owner, repo, title, body});
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: created.data.number,
|
||||
labels: ['release', 'packaging', 'daily-stable', 'automated']
|
||||
});
|
||||
} catch (error) {
|
||||
core.warning(`Could not add labels: ${error.message}`);
|
||||
}
|
||||
@ -35,6 +35,7 @@ EXPECTED_UPLOADS = {
|
||||
"impstats_push_victoriametrics.yml": 1,
|
||||
"debian_daily_stable.yml": 2,
|
||||
"ubuntu_daily_stable.yml": 2,
|
||||
"el10_daily_stable.yml": 2,
|
||||
}
|
||||
TEST_COMMAND_RE = re.compile(r"run-ci\.sh|make\s+[^\n]*\b(?:check|distcheck)\b|devtools/test-[^\s]+\.sh")
|
||||
UPLOAD_RE = re.compile(
|
||||
|
||||
437
devtools/release/rpm-daily-stable.sh
Executable file
437
devtools/release/rpm-daily-stable.sh
Executable file
@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: rpm-daily-stable.sh <command> [args...]
|
||||
|
||||
Commands:
|
||||
version
|
||||
prepare-sources <baseline-dir> <dist-tarball> <output-dir> <policy-file> <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>
|
||||
verify-repo <repo-url> <arch> <expected-evr> <expected-fingerprint>
|
||||
manifest <artifact-dir> <expected-evr> <arch> <channel> <distro> <distro-version>
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
base_version() {
|
||||
local configure_file script_dir
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
configure_file="${RSYSLOG_SOURCE_DIR:-$script_dir/../..}/configure.ac"
|
||||
sed -n 's/AC_INIT(\[rsyslog\],\[\([^]]*\)\].*/\1/p' "$configure_file" |
|
||||
sed 's/\.daily$//'
|
||||
}
|
||||
|
||||
short_commit_sha() {
|
||||
local candidate="${SOURCE_GIT_SHA:-${GITHUB_SHA:-}}"
|
||||
|
||||
if [ -n "$candidate" ] &&
|
||||
printf '%s\n' "$candidate" | grep -Eq '^[0-9a-fA-F]{12,64}$'; then
|
||||
printf '%.12s\n' "$candidate" | tr '[:upper:]' '[:lower:]'
|
||||
return
|
||||
fi
|
||||
|
||||
git rev-parse --verify HEAD >/dev/null 2>&1 ||
|
||||
die "could not determine git commit"
|
||||
git rev-parse --short=12 HEAD
|
||||
}
|
||||
|
||||
cmd_version() {
|
||||
local version date run attempt short_sha release expected_evr
|
||||
|
||||
version="$(base_version)"
|
||||
[ -n "$version" ] || die "could not determine base version"
|
||||
date="${RSYSLOG_BUILD_DATE:-$(date -u +%Y%m%d)}"
|
||||
printf '%s\n' "$date" | grep -Eq '^[0-9]{8}$' ||
|
||||
die "RSYSLOG_BUILD_DATE must use YYYYMMDD"
|
||||
run="${GITHUB_RUN_NUMBER:-0}"
|
||||
attempt="${GITHUB_RUN_ATTEMPT:-1}"
|
||||
short_sha="$(short_commit_sha)"
|
||||
release="0.daily${date}.${run}.${attempt}.git${short_sha}.adiscon1"
|
||||
expected_evr="${version}-${release}.el10"
|
||||
|
||||
printf '%s\n' "$expected_evr"
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
{
|
||||
printf 'version=%s\n' "$version"
|
||||
printf 'release=%s\n' "$release"
|
||||
printf 'expected_evr=%s\n' "$expected_evr"
|
||||
printf 'archive_date=%s-%s-%s\n' \
|
||||
"${date:0:4}" "${date:4:2}" "${date:6:2}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_prepare_sources() {
|
||||
local baseline_dir="$1"
|
||||
local dist_tarball="$2"
|
||||
local output_dir="$3"
|
||||
local policy_file="$4"
|
||||
local version="$5"
|
||||
local release="$6"
|
||||
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"
|
||||
|
||||
rm -rf "$output_dir"
|
||||
sources_dir="$output_dir/SOURCES"
|
||||
specs_dir="$output_dir/SPECS"
|
||||
mkdir -p "$sources_dir" "$specs_dir"
|
||||
|
||||
find "$baseline_dir" -maxdepth 1 -type f \
|
||||
! -name rsyslog.spec ! -name sources \
|
||||
-exec cp -a {} "$sources_dir/" \;
|
||||
cp "$baseline_dir/rsyslog.spec" "$specs_dir/rsyslog.spec"
|
||||
spec_file="$specs_dir/rsyslog.spec"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' RETURN
|
||||
tar -xzf "$dist_tarball" -C "$tmp_dir"
|
||||
source_root="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d -print -quit)"
|
||||
[ -n "$source_root" ] || die "dist tarball has no source directory"
|
||||
[ "$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ] ||
|
||||
die "dist tarball must contain exactly one source directory"
|
||||
mv "$source_root" "$tmp_dir/rsyslog-$version"
|
||||
tar -C "$tmp_dir" -czf "$sources_dir/rsyslog-$version.tar.gz" \
|
||||
"rsyslog-$version"
|
||||
|
||||
python3 - "$spec_file" "$policy_file" "$version" "$release" <<'PY'
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
spec_path = pathlib.Path(sys.argv[1])
|
||||
policy_path = pathlib.Path(sys.argv[2])
|
||||
version = sys.argv[3]
|
||||
release = sys.argv[4]
|
||||
spec = spec_path.read_text(encoding="utf-8")
|
||||
policy = json.loads(policy_path.read_text(encoding="utf-8"))
|
||||
|
||||
patches = {}
|
||||
for match in re.finditer(r"(?m)^Patch(\d+):\s*(\S+)\s*$", spec):
|
||||
patches[match.group(2)] = match.group(1)
|
||||
|
||||
allowed = policy.get("allowed_patch_skips", [])
|
||||
allowed_names = [entry.get("patch", "").strip() for entry in allowed]
|
||||
if any(not name for name in allowed_names):
|
||||
raise SystemExit("policy contains an empty allowed patch name")
|
||||
missing = sorted(set(allowed_names) - set(patches))
|
||||
if missing:
|
||||
raise SystemExit(f"allowed patch is absent from EL10 baseline: {missing}")
|
||||
|
||||
for name in allowed_names:
|
||||
number = patches[name]
|
||||
spec, declaration_count = re.subn(
|
||||
rf"(?m)^Patch{re.escape(number)}:\s*{re.escape(name)}\s*\n", "", spec
|
||||
)
|
||||
spec, application_count = re.subn(
|
||||
rf"(?m)^%patch\s+-P{re.escape(number)}(?:\s+[^\n]*)?\s*\n", "", spec
|
||||
)
|
||||
if declaration_count != 1 or application_count != 1:
|
||||
raise SystemExit(
|
||||
f"could not remove exactly one declaration/application for {name}"
|
||||
)
|
||||
|
||||
build_requires = policy.get("supplemental_build_requires", [])
|
||||
packages = [entry.get("package", "").strip() for entry in build_requires]
|
||||
if any(not package for package in packages):
|
||||
raise SystemExit("policy contains an empty supplemental BuildRequires package")
|
||||
for package in packages:
|
||||
if re.search(rf"(?m)^BuildRequires:\s*{re.escape(package)}(?:\s|$)", spec):
|
||||
continue
|
||||
spec, count = re.subn(
|
||||
r"(?m)^(BuildRequires:\s*autoconf\s*)$",
|
||||
rf"\1\nBuildRequires: {package}",
|
||||
spec,
|
||||
count=1,
|
||||
)
|
||||
if count != 1:
|
||||
raise SystemExit(f"could not insert supplemental BuildRequires: {package}")
|
||||
|
||||
spec, version_count = re.subn(r"(?m)^Version:\s*.*$", f"Version: {version}", spec)
|
||||
spec, release_count = re.subn(
|
||||
r"(?m)^Release:\s*.*$", f"Release: {release}%{{?dist}}", spec
|
||||
)
|
||||
spec, source_count = re.subn(
|
||||
r"(?m)^Source0:\s*.*$", "Source0: %{name}-%{version}.tar.gz", spec
|
||||
)
|
||||
if (version_count, release_count, source_count) != (1, 1, 1):
|
||||
raise SystemExit("EL10 spec version/source fields did not match exactly once")
|
||||
|
||||
stamp = datetime.now(timezone.utc).strftime("%a %b %d %Y")
|
||||
entry = (
|
||||
f"%changelog\n* {stamp} Adiscon package maintainers "
|
||||
f"<release-bot@adiscon.com> - {version}-{release}\n"
|
||||
"- Automated rsyslog Enterprise Linux 10 daily stable build.\n\n"
|
||||
)
|
||||
spec, changelog_count = re.subn(r"(?m)^%changelog\s*$", entry.rstrip(), spec, count=1)
|
||||
if changelog_count != 1:
|
||||
raise SystemExit("EL10 spec has no unique %changelog marker")
|
||||
|
||||
spec_path.write_text(spec, encoding="utf-8")
|
||||
PY
|
||||
|
||||
while IFS= read -r source_url; do
|
||||
case "$source_url" in
|
||||
http://*|https://*)
|
||||
curl --fail --location --retry 4 --retry-delay 5 \
|
||||
--output "$sources_dir/${source_url##*/}" "$source_url"
|
||||
;;
|
||||
esac
|
||||
done < <(
|
||||
rpmspec -P "$spec_file" |
|
||||
sed -n 's/^Source[1-9][0-9]*:[[:space:]]*//p'
|
||||
)
|
||||
|
||||
rm -rf "$tmp_dir"
|
||||
trap - RETURN
|
||||
}
|
||||
|
||||
cmd_build_package() {
|
||||
local prepared_dir="$1"
|
||||
local mock_config="$2"
|
||||
local artifact_dir="$3"
|
||||
local build_log="$4"
|
||||
local expected_evr="$5"
|
||||
local srpm rpm_file actual_evr rc
|
||||
|
||||
rm -rf "$artifact_dir"
|
||||
mkdir -p "$artifact_dir/srpm" "$artifact_dir/rpms"
|
||||
|
||||
set +e
|
||||
mock -r "$mock_config" --clean --buildsrpm \
|
||||
--spec "$prepared_dir/SPECS/rsyslog.spec" \
|
||||
--sources "$prepared_dir/SOURCES" \
|
||||
--resultdir "$artifact_dir/srpm" 2>&1 | tee "$build_log"
|
||||
rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
[ "$rc" -eq 0 ] || return "$rc"
|
||||
|
||||
srpm="$(find "$artifact_dir/srpm" -maxdepth 1 -type f -name '*.src.rpm' -print -quit)"
|
||||
[ -n "$srpm" ] || die "mock did not produce a source RPM"
|
||||
|
||||
set +e
|
||||
mock -r "$mock_config" --clean --rebuild "$srpm" \
|
||||
--resultdir "$artifact_dir/rpms" 2>&1 | tee -a "$build_log"
|
||||
rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
[ "$rc" -eq 0 ] || return "$rc"
|
||||
|
||||
rpm_file="$(find "$artifact_dir/rpms" -maxdepth 1 -type f \
|
||||
-name 'rsyslog-[0-9]*.x86_64.rpm' ! -name '*-debuginfo-*' -print -quit)"
|
||||
[ -n "$rpm_file" ] || die "mock did not produce the base rsyslog RPM"
|
||||
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"
|
||||
|
||||
cp "$build_log" "$artifact_dir/build.log"
|
||||
cp "$prepared_dir/SPECS/rsyslog.spec" "$artifact_dir/rsyslog.spec"
|
||||
}
|
||||
|
||||
cmd_sign_rpms() {
|
||||
local artifact_dir="$1"
|
||||
local fingerprint="$2"
|
||||
local rpm_file rpm_check
|
||||
|
||||
[ -n "$fingerprint" ] || die "RPM signing fingerprint is empty"
|
||||
command -v rpmsign >/dev/null || die "rpmsign is not installed"
|
||||
|
||||
while IFS= read -r -d '' rpm_file; do
|
||||
rpmsign --addsign \
|
||||
--define "_gpg_name $fingerprint" \
|
||||
--define '__gpg /usr/bin/gpg' \
|
||||
"$rpm_file"
|
||||
[ "$(rpm -qp --qf '%{RSAHEADER:pgpsig}' "$rpm_file")" != '(none)' ] ||
|
||||
die "RPM has no RSA header signature: $rpm_file"
|
||||
rpm_check="$(rpm --checksig --verbose "$rpm_file" 2>&1 || true)"
|
||||
printf '%s\n' "$rpm_check" | grep -q 'Payload SHA256 digest: OK' ||
|
||||
die "RPM signature verification failed: $rpm_file"
|
||||
done < <(find "$artifact_dir" -type f -name '*.rpm' -print0)
|
||||
}
|
||||
|
||||
create_repo_generation() {
|
||||
local package_dir="$1"
|
||||
local previous_dir="$2"
|
||||
local output_dir="$3"
|
||||
local package_kind="$4"
|
||||
local new_dir merge_dir
|
||||
|
||||
new_dir="$(mktemp -d)"
|
||||
merge_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$new_dir" "$merge_dir"' RETURN
|
||||
mkdir -p "$new_dir/Packages"
|
||||
case "$package_kind" in
|
||||
binary)
|
||||
find "$package_dir" -maxdepth 1 -type f -name '*.rpm' \
|
||||
! -name '*.src.rpm' -exec cp -a {} "$new_dir/Packages/" \;
|
||||
;;
|
||||
source)
|
||||
find "$package_dir" -maxdepth 1 -type f -name '*.src.rpm' \
|
||||
-exec cp -a {} "$new_dir/Packages/" \;
|
||||
;;
|
||||
*) die "unknown repository package kind: $package_kind" ;;
|
||||
esac
|
||||
createrepo_c --quiet "$new_dir"
|
||||
|
||||
rm -rf "$output_dir"
|
||||
mkdir -p "$output_dir/Packages"
|
||||
cp -a "$new_dir/Packages/." "$output_dir/Packages/"
|
||||
if [ -d "$previous_dir/repodata" ]; then
|
||||
mergerepo_c --all --omit-baseurl \
|
||||
--repo "file://$previous_dir" \
|
||||
--repo "file://$new_dir" \
|
||||
--outputdir "$merge_dir"
|
||||
cp -a "$merge_dir/repodata" "$output_dir/repodata"
|
||||
else
|
||||
cp -a "$new_dir/repodata" "$output_dir/repodata"
|
||||
fi
|
||||
|
||||
rm -rf "$new_dir" "$merge_dir"
|
||||
trap - RETURN
|
||||
}
|
||||
|
||||
cmd_generate_repo() {
|
||||
local artifact_dir="$1"
|
||||
local repo_dir="$2"
|
||||
local arch="$3"
|
||||
local fingerprint="$4"
|
||||
local passphrase_file="$5"
|
||||
local previous_dir work_dir repo_subdir
|
||||
|
||||
[ -n "$fingerprint" ] || die "repository signing fingerprint is empty"
|
||||
[ -n "$repo_dir" ] || die "repository output directory is empty"
|
||||
[ -n "$arch" ] || die "repository architecture is empty"
|
||||
[ -f "$passphrase_file" ] || die "missing repository signing passphrase file"
|
||||
command -v createrepo_c >/dev/null || die "createrepo_c is not installed"
|
||||
command -v mergerepo_c >/dev/null || die "mergerepo_c is not installed"
|
||||
|
||||
previous_dir="$(mktemp -d)"
|
||||
work_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$previous_dir" "$work_dir"' RETURN
|
||||
for repo_subdir in "$arch" SRPMS; do
|
||||
mkdir -p "$previous_dir/$repo_subdir"
|
||||
if [ -d "$repo_dir/$repo_subdir/repodata" ]; then
|
||||
cp -a "$repo_dir/$repo_subdir/repodata" \
|
||||
"$previous_dir/$repo_subdir/repodata"
|
||||
fi
|
||||
done
|
||||
|
||||
create_repo_generation \
|
||||
"$artifact_dir/rpms" "$previous_dir/$arch" "$work_dir/$arch" binary
|
||||
create_repo_generation \
|
||||
"$artifact_dir/srpm" "$previous_dir/SRPMS" "$work_dir/SRPMS" source
|
||||
|
||||
rm -rf "${repo_dir:?}/$arch" "$repo_dir/SRPMS"
|
||||
mkdir -p "$repo_dir"
|
||||
mv "$work_dir/$arch" "$repo_dir/$arch"
|
||||
mv "$work_dir/SRPMS" "$repo_dir/SRPMS"
|
||||
|
||||
for repo_subdir in "$arch" SRPMS; do
|
||||
gpg --batch --yes --pinentry-mode loopback \
|
||||
--passphrase-file "$passphrase_file" \
|
||||
--local-user "$fingerprint" \
|
||||
--armor --detach-sign \
|
||||
--output "$repo_dir/$repo_subdir/repodata/repomd.xml.asc" \
|
||||
"$repo_dir/$repo_subdir/repodata/repomd.xml"
|
||||
done
|
||||
gpg --batch --armor --export "$fingerprint" \
|
||||
> "$repo_dir/rsyslog-archive-keyring.asc"
|
||||
|
||||
rm -rf "$previous_dir" "$work_dir"
|
||||
trap - RETURN
|
||||
}
|
||||
|
||||
cmd_verify_repo() {
|
||||
local repo_url="$1"
|
||||
local arch="$2"
|
||||
local expected_evr="$3"
|
||||
local expected_fingerprint="$4"
|
||||
local verify_dir actual_fingerprint
|
||||
|
||||
verify_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$verify_dir"' RETURN
|
||||
curl --fail --silent --show-error --location \
|
||||
"$repo_url/rsyslog-archive-keyring.asc" --output "$verify_dir/key.asc"
|
||||
actual_fingerprint="$(
|
||||
gpg --batch --show-keys --with-colons "$verify_dir/key.asc" |
|
||||
awk -F: '$1 == "fpr" { print $10; exit }'
|
||||
)"
|
||||
[ "$actual_fingerprint" = "$expected_fingerprint" ] ||
|
||||
die "archive key fingerprint $actual_fingerprint does not match expected fingerprint"
|
||||
gpg --batch --no-default-keyring --keyring "$verify_dir/keyring.gpg" \
|
||||
--import "$verify_dir/key.asc" >/dev/null 2>&1
|
||||
curl --fail --silent --show-error --location \
|
||||
"$repo_url/$arch/repodata/repomd.xml" --output "$verify_dir/repomd.xml"
|
||||
curl --fail --silent --show-error --location \
|
||||
"$repo_url/$arch/repodata/repomd.xml.asc" --output "$verify_dir/repomd.xml.asc"
|
||||
gpgv --keyring "$verify_dir/keyring.gpg" \
|
||||
"$verify_dir/repomd.xml.asc" "$verify_dir/repomd.xml"
|
||||
printf 'Verified signed repository metadata for %s (%s)\n' \
|
||||
"$expected_evr" "$arch"
|
||||
|
||||
rm -rf "$verify_dir"
|
||||
trap - RETURN
|
||||
}
|
||||
|
||||
cmd_manifest() {
|
||||
local artifact_dir="$1"
|
||||
local expected_evr="$2"
|
||||
local arch="$3"
|
||||
local channel="$4"
|
||||
local distro="$5"
|
||||
local distro_version="$6"
|
||||
|
||||
python3 - "$artifact_dir" "$expected_evr" "$arch" "$channel" \
|
||||
"$distro" "$distro_version" <<'PY'
|
||||
import hashlib
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
manifest = {
|
||||
"version": sys.argv[2],
|
||||
"architecture": sys.argv[3],
|
||||
"channel": sys.argv[4],
|
||||
"distribution": sys.argv[5],
|
||||
"distribution_version": sys.argv[6],
|
||||
"files": [],
|
||||
}
|
||||
for path in sorted(root.rglob("*")):
|
||||
if not path.is_file() or path.name in {"manifest.json", "SHA256SUMS"}:
|
||||
continue
|
||||
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
manifest["files"].append(
|
||||
{"path": str(path.relative_to(root)), "sha256": digest, "size": path.stat().st_size}
|
||||
)
|
||||
(root / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||
with (root / "SHA256SUMS").open("w", encoding="utf-8") as output:
|
||||
for entry in manifest["files"]:
|
||||
output.write(f"{entry['sha256']} {entry['path']}\n")
|
||||
PY
|
||||
}
|
||||
|
||||
command="${1:-}"
|
||||
case "$command" in
|
||||
version) shift; cmd_version "$@" ;;
|
||||
prepare-sources) shift; cmd_prepare_sources "$@" ;;
|
||||
build-package) shift; cmd_build_package "$@" ;;
|
||||
sign-rpms) shift; cmd_sign_rpms "$@" ;;
|
||||
generate-repo) shift; cmd_generate_repo "$@" ;;
|
||||
verify-repo) shift; cmd_verify_repo "$@" ;;
|
||||
manifest) shift; cmd_manifest "$@" ;;
|
||||
*) usage; exit 2 ;;
|
||||
esac
|
||||
Loading…
x
Reference in New Issue
Block a user