RPM Pipeline
Explanation of the RPM build pipeline, from spec file changes through validation, building, signing, and publishing to the Hummingbird package repository.
Overview
The RPM pipeline consists of five main stages:
- Spec Change - A package spec file is modified in
rpms/<package>/ - Merge Request & Validation - CI validates the change
- Build - Konflux builds RPMs per architecture via mock in hermetic mode
- Signing - Built RPMs are cryptographically signed
- Publishing - Signed RPMs are uploaded to the Hummingbird Pulp repository
After publishing, updated RPMs are picked up by the container image pipeline via lockfile updates.
flowchart TD
A["Spec Change<br/>rpms/<package>/"] --> B["Merge Request"]
B --> C["CI Validation<br/>(check, tree_status)"]
C --> D["Merge to main"]
D --> E["Konflux RPM Build<br/>Tekton PipelineRun per package<br/>mock hermetic build per arch"]
E --> F["RPM Signing<br/>Kerberos-based"]
F --> G["Publish to Pulp<br/>packages.redhat.com<br/>/public-hummingbird/<arch>/"]
G --> H["Container lockfile updates<br/>(see Image Pipeline)"]
Stage 1: Spec Change
An RPM change begins as a commit in rpms/<package>/. The change typically
includes a modified .spec file (updated version, new patch, or Release bump)
and any associated source files or patches.
How changes originate
| Method | Description | Modification Status |
|---|---|---|
| Automated Fedora sync | ci/dist_git.py update merges upstream Fedora changes |
No independent packages |
| Upstream version update | ci/check_upstream_versions.py --update bumps to new upstream release |
No independent packages |
| Manual patch backport | Developer adds a CVE patch and references it in the .spec |
Marked modified afterward |
| No-change rebuild | ci/dist_git.py rebuild <package> bumps the Release field |
Status unchanged |
| Reverse dependency rebuild | ci/dist_git.py rebuild-rev-deps <package> rebuilds all dependents |
Status unchanged |
See Rebuilding Packages and Updating Dist-git Packages for detailed workflows.
Package metadata
Each package has a metadata file at metadata/<package>.json that tracks its
relationship to upstream:
{
"source": "https://src.fedoraproject.org/rpms/curl.git",
"branch": "rawhide",
"sha": "abc123...",
"version": "8.12.1",
"release": "1",
"modification_status": "clean",
"upstream_repo": "https://github.com/curl/curl"
}
Key fields:
| Field | Purpose |
|---|---|
modification_status |
clean (auto-updates enabled), modified (auto-updates blocked), or independent (no upstream) |
modification_reason |
Explanation of local modifications (when modified) |
release |
Base release without dist tag — Fedora/rawhide baseline, or a local base (independents / ahead-of-Fedora) |
upstream_repo |
Canonical upstream git repository URL |
track_upstream |
Version prefix constraint (e.g., "1.26" for golang1.26) |
version_transform |
Version mapping rule for CVE analysis (e.g., dotnet_sdk_to_runtime) |
cve_product |
CVE vendor/product override (string, or a list for multiple products; e.g. "Oracle Corporation / Oracle Java SE") |
See Package Metadata Fields for modification_status and release
configuration, and Package Modification Tracking for managing modification
status day to day.
Stage 2: Merge Request & Validation
Changes reach main via merge requests. Automated MRs are created using:
ci/create_mr.sh- Single MR with configurable optionsci/rebuild_multi_mr.sh- One MR per package with auto-merge enabledci/dist_git_update_multi_mr.sh- Automated Fedora update MRs (scheduled CI job)ci/upstream_update_multi_mr.sh- Automated upstream version update MRs (scheduled CI job)
CI validation
GitLab CI runs validation jobs on every merge request:
check- Linting, type checking, and spec validationcheck_nevr_conflicts- Predicts the NEVR(A) each changed package’s spec would build (usingrpmspecwith the real dist-tag macros, without actually building) and checks it against the public Pulp repo. Catches the case where a Konflux build succeeds and the MR merges, but the resulting NEVR was already published (typically two independent Release bumps based on stalemain, e.g. tworebuildMRs), which fails the post-merge build+sign+publish pipeline since Pulp treats each NEVRA as unique and immutable. Only scheduled for MRs that touchrpms/**/*, and within that, only checks packages whoserpms/<package>/directory itself changed – matching Konflux’s own build trigger – not packages whose only change is undermetadata/, since a metadata-only change (e.g.ci/dist_git.py mark-modified) never causes a rebuild/republish. Run locally withmake check-nevr-conflicts ARGS='<package>', or against real built RPMs withmake check-nevr-conflicts ARGS='--rpms-dir builds/<package>'afterci/build_rpms.sh. Seeci/check_nevr_conflicts.py’s module docstring for full details. Known limitations: only the public signed Pulp domain is checked (packages routed to aprivate_productare noted but not fully verified, since their domains require Pulp API credentials this tool doesn’t have); and since it reflects Pulp’s state at job-run time, it reduces but cannot fully close the race where another MR publishes the same NEVR between this check passing and this MR’s eventual merge – only a hard check inside the actual Konflux publish pipeline (a separate pipeline-bundle repository,quay.io/hummingbird-ci/rpmbuild-pipeline, not covered by this job) could close that window completely; and in spec mode someOKresults can be for a package name a real build never actually produces (e.g. a base package with no top-level%filessection, roughly 14% of packages in this repo) – expected and harmless, and never a reason to discount aCONFLICTfound elsewhere in the same run.tree_status- Repository consistency checks (metadata integrity, spec file presence)- Testing Farm - Integration tests run via an IntegrationTestScenario that exercises built RPMs on a real RHEL compose (see Testing below)
Auto-approval for chore MRs
Automated update MRs (chore/* branches) follow an accelerated path:
- MR is created with auto-merge enabled
- Konflux builds the package and posts commit statuses
- After a delay, the
chore_mr_approvalCI job auto-approves the MR - GitLab merges the MR once the pipeline succeeds
Stage 3: Build
After merging to main, Konflux builds RPMs automatically.
Build triggers
Each package has a Tekton PipelineRun definition generated from its directory
in rpms/<package>/ (see .tekton/rpms-on-pull-request.yaml.j2
and the rendered .tekton/rpms-on-pull-request.yaml).
PipelinesAsCode triggers a build when changes to
that package’s directory are pushed to main. When a single push touches
multiple packages, one PipelineRun is triggered per affected package directory.
Merge request pushes also trigger builds for validation.
The setup package acts as a canary: changes to ci/ or mock/ directories
also trigger a setup build on pull requests, validating build infrastructure
changes before they reach main.
Build process
The build pipeline (build-rpm-package) executes for each target
architecture:
- Checks out the repository at the merge commit
- Calculates build dependencies
- Runs
mockin hermetic mode (network-isolated with pre-fetched dependencies) - Produces RPM artifacts and build logs
- Generates an SBOM (Software Bill of Materials)
- Stores artifacts as Trusted Artifacts in the Konflux OCI registry
Build architectures
Packages are built for the architectures specified in their PipelineRun definition. Most packages build for x86_64 and aarch64. Some packages also build for s390x and ppc64le.
Build output
Build artifacts are stored in the Konflux OCI registry:
quay.io/redhat-user-workloads/hummingbird-tenant/<package>--main
Konflux resources
Build infrastructure is defined across two repositories:
- rpms repo (
konflux-templates/) - Per-package Component and ImageRepository resources, ReleasePlanAdmission, EnterpriseContractPolicy - infrastructure repo (
kubernetes/rpms-main/) - Application, ReleasePlan, IntegrationTestScenario, ServiceAccount, and Secret resources
See Konflux Resource Deployment for details on how these resources are managed and deployed.
Testing
RPM packages are validated through integration tests that run on Testing Farm infrastructure.
Tests run via Testing Farm on RHEL-9-Nightly systems for both
x86_64 and aarch64 architectures. Test results appear as external jobs in
GitLab CI pipelines, providing pass/fail status and links to the Konflux
PipelineRun.
Test triggering
Tests are only triggered on merge requests, not on main branch builds. For a
package at rpms/<package>/, tests are triggered for all changes below that
directory.
Integration Test Scenario
Tests are triggered via an IntegrationTestScenario resource defined in the
infrastructure repository:
Configuration: infrastructure/kubernetes/rpms-main/10-integration-test-scenarios-testing-farm.yml.j2
The scenario uses the upstream Testing Farm pipeline for Konflux CI and is parameterized as follows:
Scenario parameters:
COMPOSE: RHEL-9-Nightly- Test environment OS/composePIPELINE_MODE: rpm- Configures RPM testing mode (vs. container)ARCH: x86_64,aarch64- Architectures to test (creates separate test runs per arch)PASS_SNAPSHOT_TO_TF: false- Don’t pass full snapshot JSON (package info comes viaIMAGE_NAME/IMAGE_URLinstead)IMAGE_TAG:- Version of the tmt-via-testing-farm pipeline bundle
Key characteristics:
- Single scenario for all packages - Not per-package, runs for every package build
- Context-based triggering - Runs on
pull_requestsnapshots only - Package identification - Konflux automatically provides
IMAGE_NAME(e.g.,openssl-main) andIMAGE_URL(OCI image with built RPMs) as environment variables
FMF test plan
The root folder of the rpms repository is marked with a .fmf directory to
identify it as an FMF metadata tree for tmt. The test plan in
ci/run_tests_rpm.fmf sets up the test instance and runs default and
package-specific tests via ci/run_tests_rpm.sh.
Test environment
These environment variables are available to the tmt test execution:
| Variable | Description |
|---|---|
IMAGE_NAME |
Component name (e.g., openssl-main) |
IMAGE_URL |
OCI image URL with built RPMs (e.g., quay.io/.../openssl-main@sha...) |
SNAPSHOT |
Snapshot metadata (if PASS_SNAPSHOT_TO_TF is true) |
COMPOSE |
OS/compose being tested (RHEL-9-Nightly) |
ARCH |
Architectures being tested (x86_64,aarch64) |
TMT_VERSION |
tmt version running the tests |
Stage 4: Signing
The Konflux Release Service signs built RPMs before publishing:
- Method: Kerberos-based signing via
konflux-release-signing-prod@IPA.REDHAT.COM - Pipeline: Signing runs as part of the release pipeline, before the Pulp upload step
- Signing image:
quay.io/konflux-ci/signing:latest(verify pinned digest in ReleasePlanAdmission)
Stage 5: Publishing to Pulp
After signing, RPMs are uploaded to the Hummingbird Pulp repository.
Release pipeline
The push-rpms-to-pulp release pipeline handles the upload. Configuration is
defined in the ReleasePlanAdmission resource
(releng/hummingbird-rpms-tech-preview-staging.yaml):
mapping:
rpm-repositories:
- name: x86_64
repository_id: public-hummingbird-x86_64-rpms
- name: aarch64
repository_id: public-hummingbird-aarch64-rpms
- name: src
repository_id: public-hummingbird-source-rpms
# See ReleasePlanAdmission for the full list of published architectures
Pulp repository
Published RPMs are available in the Pulp content index:
Pulp automatically regenerates repository metadata (repodata) after each upload, making new packages immediately resolvable by DNF/YUM clients.
What Happens Next
Once RPMs are available in Pulp, the container image pipeline picks them up via lockfile updates. See the Image Pipeline documentation in the containers repo for the full container lifecycle.
Related Documentation
- Rebuilding Packages - No-change rebuilds, reverse dependency rebuilds, and patch backports
- Updating Dist-git Packages - Automated Fedora sync workflow
- Package Metadata Fields -
modification_statusandreleaseconfiguration - Package Modification Tracking - Managing modification status and version constraints
- Konflux Resource Deployment - How Konflux resources are defined and deployed
- Image Pipeline - Container image pipeline (continues from where this document ends)