Build and verify development RPMs with Gitea Actions
Build development RPM / rpm (push) Failing after 17s
Build development RPM / rpm (push) Failing after 17s
This commit is contained in:
@@ -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.
|
||||
@@ -13,3 +13,7 @@ __pycache__/
|
||||
secrets/
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# RPM build trees and development artifacts
|
||||
.build/
|
||||
dist/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
0.1.0
|
||||
@@ -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
|
||||
Executable
+43
@@ -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"
|
||||
@@ -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')
|
||||
Reference in New Issue
Block a user