Build and verify development RPMs with Gitea Actions
Build development RPM / rpm (push) Failing after 17s

This commit is contained in:
Lyncoln
2026-09-12 01:48:39 -04:00
parent 4a358ae0f6
commit c8636ed6d4
10 changed files with 316 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
name: Build development RPM
on:
push:
branches: ['main']
pull_request:
workflow_dispatch:
jobs:
rpm:
runs-on: ubuntu-latest
container:
image: registry.fedoraproject.org/fedora:43
steps:
- name: Install build tools
run: |
dnf -y install rpm-build python3 resource-agents git tar gzip diffutils findutils coreutils bash nodejs
- name: Check out source
uses: https://github.com/actions/checkout@v4
with:
persist-credentials: false
- name: Build and check RPMs
run: bash scripts/build-rpm.sh
- name: Test installation in the disposable job container
run: |
dnf -y install ./dist/*.noarch.rpm
python3 tests/verify_installed_rpm.py
# v3 uses the artifact protocol supported by older act_runner instances.
- name: Save development packages
uses: https://github.com/actions/upload-artifact@v3
with:
name: ocf-managed-container-fedora43
path: dist/
if-no-files-found: error
retention-days: 14
# No registry uploads, release creation, or deployment while licensing is pending.
+4
View File
@@ -13,3 +13,7 @@ __pycache__/
secrets/
*.pem
*.key
# RPM build trees and development artifacts
.build/
dist/
+8
View File
@@ -42,3 +42,11 @@ the frozen reference or automatically run destructive HA tests.
```bash
git clone ssh://git@gitea.loxthelion.com:2222/david/ocf-managed-container.git
```
## RPM builds
Gitea Actions builds development RPMs on `main` pushes and pull requests using
an `ubuntu-latest` runner with a Fedora 43 job container. See
[packaging instructions](packaging/README.md) for local builds and artifacts.
[License provenance remains pending](packaging/PROVENANCE.md); release publishing
and deployment are not configured.
+3
View File
@@ -0,0 +1,3 @@
FROM registry.fedoraproject.org/fedora:43
RUN dnf -y install rpm-build python3 resource-agents git tar gzip diffutils findutils \
coreutils bash nodejs && dnf clean all
+40
View File
@@ -0,0 +1,40 @@
# Licensing and provenance status
License selection is pending. No MIT, Apache, or GPL license has been granted
for this project by this document. `LicenseRef-Provenance-Pending` in the draft
RPM spec is a local status marker, not an assertion of an approved license.
Builds are development artifacts; this workflow does not publish releases or
upload to an RPM registry.
The original development conversation is:
https://chatgpt.com/share/6aa4e495-3e6c-83e9-a13c-fc65a972146b
The relevant discussion references the OCF developer guide and ClusterLabs'
`heartbeat/podman`, `heartbeat/docker`, `heartbeat/anything`, and
`heartbeat/pgsql` agents. One early proposal describes Podman as a model/base;
other passages discuss combining concepts from the container and PostgreSQL
agents. These establish influence, not proof of copied implementation.
On 2026-09-12 a preliminary comparison of the frozen reference against the
current upstream main versions of those four agents found only short exact
runs after trimming whitespace and omitting blank lines (maximum 3–4 lines).
The inspected matches were ordinary XML and shell control-flow fragments.
This is not a historical-version audit and does not exclude adapted code.
Source references:
- https://github.com/ClusterLabs/resource-agents/blob/main/doc/dev-guides/ra-dev-guide.asc
- https://github.com/ClusterLabs/resource-agents/blob/main/heartbeat/podman
- https://github.com/ClusterLabs/resource-agents/blob/main/heartbeat/docker
- https://github.com/ClusterLabs/resource-agents/blob/main/heartbeat/anything
- https://github.com/ClusterLabs/resource-agents/blob/main/heartbeat/pgsql
The inspected podman, docker and anything headers specify GPL version 2.
The inspected pgsql header says GPL without a version. The developer guide
explains that LGPL-licensed ocf-shellfuncs can be used by non-GPL agents.
The RPM depends on the system OCF library and does not bundle that library.
Before a licensed release, resolve the provenance of any adapted material,
retain required notices, select an appropriate license for project-owned
contributions, add the actual license text, and replace the draft RPM marker.
The private development conversation itself is not bundled in source archives.
+74
View File
@@ -0,0 +1,74 @@
# Development RPM builds
The Gitea workflow selects the `ubuntu-latest` runner and runs the build inside
Fedora 43, matching the saved lab package context. The workstation currently
reports Fedora 44; this first workflow does not claim a Fedora 44 build test.
It runs on main pushes, pull requests and manual dispatch.
The build installs only the OCF executable and nine modules, plus documentation.
It includes no Quadlet, secrets, databases or cluster configuration. There are
no RPM lifecycle scriptlets. Installation does not start, stop or restart a
service or change maintenance mode. Host PostgreSQL packages are not required.
The draft LicenseRef-Provenance-Pending field is a status marker, not a license
grant. See PROVENANCE.md. Packages are unsigned development artifacts. The
workflow does not publish a release or upload to the Gitea RPM registry.
## Local build
In a disposable Fedora 43 build environment, install the packages listed in
Containerfile, then run from a Git checkout:
```bash
bash scripts/build-rpm.sh
```
Artifacts are written to dist/: a noarch binary RPM, a source RPM and SHA256SUMS.
Build trees remain under .build/ for inspection. The script packages the current
working tree; use a clean checkout for a build identified by its Git revision.
Only explicitly listed project directories enter the source archive. Lab
snapshots, local agent configuration, .git and secrets are excluded.
Version comes from packaging/VERSION. Development Release values are
`0.<UTC commit timestamp>.git<12-character commit>`, plus the Fedora dist suffix.
A future release with Release 1 will sort after these development builds of the
same version. SOURCE_REVISION and SOURCE_DATE_EPOCH can supply build identity
when testing an exported source tree; RPM_OUTPUT_DIR selects the output path.
%check runs the offline differential suite and checks the staged executable's
metadata from both the build directory and /tmp. No live HA actions run.
Actual target-node validate-all and HA transitions remain separate lab tests.
## Gitea requirements
Enable repository Actions. The runner must support container jobs and reach
Gitea, GitHub (for actions), the Fedora image registry, and Fedora repositories.
Checkout uses the job token over HTTPS; no SSH private key is required.
The workflow uses upload-artifact v3 for compatibility with older act_runner
installations. Newer Gitea runners may support v4; check the installed server
and runner versions before changing the artifact action. Artifact upload and
runner integration must be verified by the first actual Gitea run.
Before release publishing, resolve licensing, add its text to the package,
choose signing and version-tag policy, and configure registry authentication.
## Validation performed
On 2026-09-12, the working-tree candidate was built with Fedora 43 / RPM 6.0.2
in a disposable rootless Podman container. Binary and source RPM builds passed,
including all 22 offline differential scenarios and staged metadata checks.
DNF successfully resolved dependencies and installed the binary RPM inside that
container. tests/verify_installed_rpm.py verified the installed architecture,
RPM integrity, exact agent bytes, root ownership, 0755/0644 modes, allowed file
paths, absence of lifecycle scriptlets, and metadata from an unrelated cwd.
The source RPM rebuilt with the same version/release without Git metadata.
Fedora's automatic shebang rewriting is disabled for this shell-only package
so the entrypoint remains byte-identical to the tested source. The Corosync
runtime dependency is the corosync package (its cmapctl binary is in /usr/bin
on Fedora 43). No package was installed on either lab host.
The Gitea server reports 1.27.3. Its Actions API returned HTTP 401 without
authentication, so a successful local build does not establish runner checkout
or artifact upload success. Check the first run in the repository's Actions tab.
+1
View File
@@ -0,0 +1 @@
0.1.0
+69
View File
@@ -0,0 +1,69 @@
%global ocf_provider_dir /usr/lib/ocf/resource.d/loxthelion
# Preserve the proven entrypoint byte-for-byte, including its /bin/bash shebang.
%global __brp_mangle_shebangs %{nil}
%{!?package_version:%global package_version 0.1.0}
%{!?package_release:%global package_release 0.dev}
Name: ocf-managed-container
Version: %{package_version}
Release: %{package_release}%{?dist}
Summary: PostgreSQL HA agent for a persistent rootless container
# Development status only; see PROVENANCE.md. No release publishing yet.
License: LicenseRef-Provenance-Pending
URL: https://gitea.loxthelion.com/david/ocf-managed-container
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
BuildRequires: bash
BuildRequires: python3
BuildRequires: resource-agents
BuildRequires: hostname
BuildRequires: diffutils
BuildRequires: coreutils
Requires: bash
Requires: /usr/lib/ocf/lib/heartbeat/ocf-shellfuncs
Requires: podman
Requires: systemd
Requires: /usr/sbin/runuser
Requires: /usr/bin/getent
Requires: corosync
Requires: coreutils
Requires: gawk
Requires: sed
Requires: hostname
%description
OCF agent managing PostgreSQL through a persistent rootless systemd/Quadlet
container, with role-aware monitoring, promotion, demotion and rejoin.
This development package has unresolved license provenance. It does not
configure the cluster or container and does not start or stop services.
%prep
%autosetup
cp packaging/README.md RPM-BUILD.md
%build
# Shell modules require no compilation.
%install
install -d %{buildroot}%{ocf_provider_dir}/managed-container.d
install -m 0755 refactored/managed-container %{buildroot}%{ocf_provider_dir}/managed-container
install -m 0644 refactored/managed-container.d/*.sh %{buildroot}%{ocf_provider_dir}/managed-container.d/
%check
python3 tests/verify_refactor.py
# Check the actual staged layout, including resolution from an unrelated cwd.
agent="%{buildroot}%{ocf_provider_dir}/managed-container"
"$agent" meta-data > staged-metadata.xml
reference/managed-container meta-data > reference-metadata.xml
cmp reference-metadata.xml staged-metadata.xml
(cd /tmp && "$agent" meta-data) > other-cwd-metadata.xml
cmp reference-metadata.xml other-cwd-metadata.xml
%files
%defattr(-,root,root,-)
%doc README.md packaging/PROVENANCE.md RPM-BUILD.md
%dir %{ocf_provider_dir}
%attr(0755,root,root) %{ocf_provider_dir}/managed-container
%dir %{ocf_provider_dir}/managed-container.d
%attr(0644,root,root) %{ocf_provider_dir}/managed-container.d/*.sh
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail
project_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$project_dir"
package_version="$(cat packaging/VERSION)"
[[ "$package_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo 'Invalid packaging/VERSION' >&2; exit 1; }
source_revision="${SOURCE_REVISION:-$(git rev-parse HEAD)}"
[[ "$source_revision" =~ ^[0-9a-f]{40,64}$ ]] || { echo 'Invalid SOURCE_REVISION' >&2; exit 1; }
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)}"
[[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]] || { echo 'Invalid SOURCE_DATE_EPOCH' >&2; exit 1; }
package_release="0.$(date -u -d "@$SOURCE_DATE_EPOCH" +%Y%m%d%H%M%S).git${source_revision:0:12}"
output_dir="${RPM_OUTPUT_DIR:-$project_dir/dist}"
mkdir -p "$output_dir" "$project_dir/.build"
output_dir="$(CDPATH= cd -- "$output_dir" && pwd)"
build_dir="$(mktemp -d "$project_dir/.build/rpm.XXXXXXXX")"
mkdir -p "$build_dir"/{SOURCES,SPECS,BUILD,BUILDROOT,RPMS,SRPMS}
# Explicit source list excludes lab snapshots, local configuration and secrets.
# Include the frozen reference and test harness so the SRPM runs the same checks.
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner \
--exclude='__pycache__' --exclude='*.pyc' \
--transform="s,^,ocf-managed-container-$package_version/," \
-czf "$build_dir/SOURCES/ocf-managed-container-$package_version.tar.gz" \
README.md HANDOFF.md refactored reference tests packaging scripts
# Freeze identity into the SRPM spec so rpmbuild --rebuild needs no Git metadata
# or external macro definitions and retains the same version and release.
sed -e "s/^Version:.*/Version: $package_version/" \
-e "s/^Release:.*/Release: $package_release%{?dist}/" \
packaging/ocf-managed-container.spec > "$build_dir/SPECS/ocf-managed-container.spec"
rpmbuild -ba --define "_topdir $build_dir" \
"$build_dir/SPECS/ocf-managed-container.spec"
for package in "$build_dir"/RPMS/noarch/*.rpm "$build_dir"/SRPMS/*.rpm; do
cp "$package" "$output_dir/"
done
rpm -qp --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\nLicense: %{LICENSE}\n' \
"$build_dir"/RPMS/noarch/*.rpm
rpm -qpl "$build_dir"/RPMS/noarch/*.rpm
# The package must never run service or cluster lifecycle scriptlets.
[[ -z "$(rpm -qp --scripts "$build_dir"/RPMS/noarch/*.rpm)" ]]
(cd "$output_dir" && sha256sum ./*.rpm > SHA256SUMS)
printf 'Development RPMs: %s\nBuild tree: %s\n' "$output_dir" "$build_dir"
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Read-only checks after installing the RPM in a disposable build container."""
from pathlib import Path
import stat
import subprocess
ROOT = Path(__file__).resolve().parents[1]
PROVIDER = Path('/usr/lib/ocf/resource.d/loxthelion')
AGENT = PROVIDER / 'managed-container'
PACKAGE = 'ocf-managed-container'
def output(*args):
return subprocess.check_output(args, text=True)
assert output('rpm', '-q', '--qf', '%{ARCH}', PACKAGE) == 'noarch'
assert output('rpm', '-q', '--scripts', PACKAGE) == ''
subprocess.run(['rpm', '-V', PACKAGE], check=True)
expected = {AGENT: ROOT / 'refactored/managed-container'}
for path in (ROOT / 'refactored/managed-container.d').glob('*.sh'):
expected[PROVIDER / 'managed-container.d' / path.name] = path
assert len(expected) == 10
for path, source in expected.items():
assert path.read_bytes() == source.read_bytes(), path
info = path.stat()
assert (info.st_uid, info.st_gid) == (0, 0), path
assert stat.S_IMODE(info.st_mode) == (0o755 if path == AGENT else 0o644), path
owned = set(output('rpm', '-ql', PACKAGE).splitlines())
allowed = {str(path) for path in expected} | {str(PROVIDER), str(PROVIDER / 'managed-container.d')}
unexpected = {path for path in owned - allowed if not path.startswith('/usr/share/doc/' + PACKAGE)}
assert not unexpected, unexpected
reference = output(str(ROOT / 'reference/managed-container'), 'meta-data')
installed = subprocess.check_output([str(AGENT), 'meta-data'], cwd='/tmp', text=True)
assert installed == reference
print('PASS installed RPM: noarch, file integrity, root ownership, modes, payload, no scriptlets, metadata/module loading')