From a73a85ebc0cc43996329651c7cebd884eb3954cc Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 8 Oct 2026 19:17:15 -0400 Subject: [PATCH] ci(server): skip image publication for GitHub prereleases --- .github/workflows/server-image.yml | 2 +- docs/releasing.md | 7 +++++-- scripts/test_server_image_release.py | 7 +++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/server-image.yml b/.github/workflows/server-image.yml index 9a25d42..b02b69b 100644 --- a/.github/workflows/server-image.yml +++ b/.github/workflows/server-image.yml @@ -114,7 +114,7 @@ jobs: publish: if: >- github.repository == 'ajisaacs/OpenNest' && - (github.event_name == 'release' || + ((github.event_name == 'release' && github.event.release.prerelease == false) || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master')) runs-on: ubuntu-latest timeout-minutes: 40 diff --git a/docs/releasing.md b/docs/releasing.md index cd3741a..ccd7d11 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -68,9 +68,12 @@ or attestations (`--provenance=false --sbom=false`), and runs the real-client container persistence/backup/restore smoke. A Windows `v*` tag build alone never publishes a server image. -Publication requires owner-approved release intent: a **published GitHub Release** +Publication requires owner-approved release intent: a **published, non-prerelease GitHub Release** or an explicit `Server image` dispatch **from `master`**, naming an existing strict -`vX.Y.Z` tag (no prerelease, leading zero, or extra suffix). The tag must resolve to +`vX.Y.Z` tag (no prerelease suffix, leading zero, or extra suffix). GitHub pre-releases +publish only the Windows assets; they skip the server image publish and verification +jobs. Changing a pre-release to a full release later is a separate publication +decision. The tag must resolve to a full commit already on `master`. For a published Release, that peeled commit must also equal the event's full `GITHUB_SHA`; a retargeted tag is refused. Manual dispatch intentionally resolves the approved existing tag, not the workflow's diff --git a/scripts/test_server_image_release.py b/scripts/test_server_image_release.py index 0f4b294..72e18c0 100644 --- a/scripts/test_server_image_release.py +++ b/scripts/test_server_image_release.py @@ -698,6 +698,13 @@ class ImmutableTagCollisions(unittest.TestCase): class WorkflowBehavior(unittest.TestCase): + def test_prerelease_skips_ghcr_publish_but_stable_release_and_manual_dispatch_remain(self): + workflow = pathlib.Path(__file__).resolve().parents[1] / ".github/workflows/server-image.yml" + publish = workflow.read_text().split("\n publish:\n", 1)[1].split("\n verify-published:\n", 1)[0] + condition = publish.split(" if: >-\n", 1)[1].split(" runs-on:", 1)[0] + self.assertIn("github.event_name == 'release' && github.event.release.prerelease == false", condition) + self.assertIn("github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'", condition) + def test_build_log_pipeline_preserves_docker_failure(self): workflow = pathlib.Path(__file__).resolve().parents[1] / ".github/workflows/server-image.yml" lines = workflow.read_text().splitlines()