From 2ebacfe380a79dd5af9a3b054c0bedd7cf450090 Mon Sep 17 00:00:00 2001 From: Brad Williams Date: Tue, 14 Jul 2026 13:43:28 -0400 Subject: [PATCH 1/2] Gate acceptance/rejection on release mirror job completion rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- .../sync_release_payload.go | 12 +- .../sync_release_payload_test.go | 64 --- .../payload_accepted_controller.go | 17 + .../payload_accepted_controller_test.go | 370 +++++++++++++++++- .../payload_rejected_controller.go | 17 + .../payload_rejected_controller_test.go | 254 ++++++++++++ 6 files changed, 665 insertions(+), 69 deletions(-) diff --git a/cmd/release-controller/sync_release_payload.go b/cmd/release-controller/sync_release_payload.go index adcbb26ff..a8fd90935 100644 --- a/cmd/release-controller/sync_release_payload.go +++ b/cmd/release-controller/sync_release_payload.go @@ -51,10 +51,6 @@ func newReleasePayload(release *releasecontroller.Release, tag *imagev1.TagRefer Namespace: jobNamespace, ReleaseCreationJobName: name, }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: jobNamespace, - ReleaseMirrorJobName: releaseMirrorJobName(name), - }, ProwCoordinates: v1alpha1.ProwCoordinates{Namespace: prowNamespace}, }, PayloadVerificationConfig: v1alpha1.PayloadVerificationConfig{ @@ -79,6 +75,14 @@ func newReleasePayload(release *releasecontroller.Release, tag *imagev1.TagRefer }} } + // We should only be populating the ReleaseMirrorCoordinates if/when they are actually defined... + if release.Config.AlternateImageRepository != "" && release.Config.AlternateImageRepositorySecretName != "" { + payload.Spec.PayloadCreationConfig.ReleaseMirrorCoordinates = v1alpha1.ReleaseMirrorCoordinates{ + Namespace: jobNamespace, + ReleaseMirrorJobName: releaseMirrorJobName(name), + } + } + // Sort the ReleaseVerification items into a consistent order var sortedKeys []string for key := range verificationJobs { diff --git a/cmd/release-controller/sync_release_payload_test.go b/cmd/release-controller/sync_release_payload_test.go index 7313fbc5c..f84c723ae 100644 --- a/cmd/release-controller/sync_release_payload_test.go +++ b/cmd/release-controller/sync_release_payload_test.go @@ -70,10 +70,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -121,10 +117,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -177,10 +169,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -234,10 +222,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -292,10 +276,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -350,10 +330,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -417,10 +393,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.12.11", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.12.11-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -492,10 +464,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.12.11", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.12.11-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -556,10 +524,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -625,10 +589,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -694,10 +654,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -768,10 +724,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -923,10 +875,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -1033,10 +981,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.11.0-0.nightly-2022-03-11-113341", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.11.0-0.nightly-2022-03-11-113341-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -1106,10 +1050,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.18.0-0.nightly-2025-01-01-000000", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.18.0-0.nightly-2025-01-01-000000-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, @@ -1172,10 +1112,6 @@ func TestNewReleasePayload(t *testing.T) { Namespace: "ci-release", ReleaseCreationJobName: "4.12.0", }, - ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ - Namespace: "ci-release", - ReleaseMirrorJobName: "4.12.0-alternate-mirror", - }, ProwCoordinates: v1alpha1.ProwCoordinates{ Namespace: "ci", }, diff --git a/pkg/cmd/release-payload-controller/payload_accepted_controller.go b/pkg/cmd/release-payload-controller/payload_accepted_controller.go index f43e6c174..9e0862eb6 100644 --- a/pkg/cmd/release-payload-controller/payload_accepted_controller.go +++ b/pkg/cmd/release-payload-controller/payload_accepted_controller.go @@ -39,6 +39,7 @@ const ( // - .spec.payloadOverride.override // - .status.blockingJobResults // - .status.releaseCreationJobResult.status +// - .status.releaseMirrorJobResult.status // // and populates the following condition: // - .status.conditions.PayloadAccepted @@ -137,6 +138,22 @@ func computeReleasePayloadAcceptedCondition(payload *v1alpha1.ReleasePayload) me return acceptedCondition } + // When a mirror job is configured, it must reach a terminal state before + // acceptance can be evaluated — including manual overrides, since the + // images need to be backed up before any decision is made. A failed + // mirror job does not block acceptance. + // Skip this gate if the payload has already been accepted or rejected — + // retroactively reverting a terminal release due to a stale mirror status + // would be disruptive for payloads that predate this check. + if payload.Spec.PayloadCreationConfig.ReleaseMirrorCoordinates.ReleaseMirrorJobName != "" && + !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadAccepted) && + !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadRejected) { + if payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobSuccess && + payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobFailed { + return acceptedCondition + } + } + // Check for "Accepted" PayloadOverride switch payload.Spec.PayloadOverride.Override { case v1alpha1.ReleasePayloadOverrideAccepted: diff --git a/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go b/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go index b4cbcadfc..baa21516c 100644 --- a/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go +++ b/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go @@ -139,6 +139,9 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{ Status: v1alpha1.ReleaseCreationJobSuccess, }, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobSuccess, + }, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, }, @@ -293,12 +296,347 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { Message: "Force reject despite success", }, }, + // === Mirror job (third precedence — after overrides) === + { + name: "MirrorJobUnsetBlocksAcceptance", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "MirrorJobUnknownBlocksAcceptance", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "MirrorJobFailedDoesNotPreventAcceptance", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobFailed, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "MirrorJobSuccessAllowsAcceptance", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobSuccess, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "NoMirrorJobConfiguredSkipsGate", + payload: &v1alpha1.ReleasePayload{ + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "AlreadyAcceptedSkipsMirrorGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-104147-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "AlreadyAcceptedWithUnsetMirrorSkipsGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-104147-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "AlreadyRejectedSkipsMirrorGateInAcceptedController", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-104147-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionFalse, + Reason: ReleasePayloadAcceptedReason, + }, + { + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionFalse, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "NotYetAcceptedStillBlockedByMirror", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-104147-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadAcceptedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "MirrorJobBlocksOverrideAccepted", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + PayloadOverride: v1alpha1.ReleasePayloadOverride{ + Override: v1alpha1.ReleasePayloadOverrideAccepted, + Reason: "Force accept", + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "MirrorJobBlocksOverrideRejected", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + PayloadOverride: v1alpha1.ReleasePayloadOverride{ + Override: v1alpha1.ReleasePayloadOverrideRejected, + Reason: "Force reject", + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadAcceptedReason, + }, + }, + { + name: "MirrorJobTerminalAllowsOverrideAccepted", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.11.0-0.nightly-2022-02-09-091559-alternate-mirror", + }, + }, + PayloadOverride: v1alpha1.ReleasePayloadOverride{ + Override: v1alpha1.ReleasePayloadOverrideAccepted, + Reason: "Force accept", + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobFailed, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadManuallyAcceptedReason, + Message: "Force accept", + }, + }, // === Blocking job results (lowest precedence) === { name: "AllBlockingJobsSuccessful", payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "job-a", AggregateState: v1alpha1.JobStateSuccess}, {CIConfigurationName: "job-b", AggregateState: v1alpha1.JobStateSuccess}, @@ -317,6 +655,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, }, @@ -333,6 +672,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateFailure}, }, @@ -349,6 +689,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStatePending}, }, @@ -365,6 +706,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, {AggregateState: v1alpha1.JobStatePending}, @@ -383,6 +725,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, {AggregateState: v1alpha1.JobStateSuccess}, @@ -401,6 +744,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, {AggregateState: v1alpha1.JobStateFailure}, @@ -419,6 +763,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStatePending}, {AggregateState: v1alpha1.JobStatePending}, @@ -436,6 +781,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateFailure}, {AggregateState: v1alpha1.JobStateFailure}, @@ -453,6 +799,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, {AggregateState: v1alpha1.JobStateUnknown}, @@ -471,6 +818,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, {AggregateState: ""}, @@ -489,7 +837,8 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, - BlockingJobResults: []v1alpha1.JobStatus{}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{}, }, }, expected: metav1.Condition{ @@ -504,6 +853,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, }, @@ -523,6 +873,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ {AggregateState: v1alpha1.JobStateSuccess}, }, @@ -543,6 +894,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a", AggregateState: v1alpha1.JobStateSuccess}, {CIConfigurationName: "informing-b", AggregateState: v1alpha1.JobStateSuccess}, @@ -560,6 +912,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a", AggregateState: v1alpha1.JobStateSuccess}, {CIConfigurationName: "informing-b", AggregateState: v1alpha1.JobStateFailure}, @@ -577,6 +930,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a", AggregateState: v1alpha1.JobStatePending}, {CIConfigurationName: "informing-b", AggregateState: v1alpha1.JobStateSuccess}, @@ -594,6 +948,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a"}, {CIConfigurationName: "informing-b"}, @@ -611,6 +966,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, UpgradeJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "upgrade-a", AggregateState: v1alpha1.JobStateSuccess}, }, @@ -627,6 +983,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, UpgradeJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "upgrade-a", AggregateState: v1alpha1.JobStatePending}, }, @@ -643,6 +1000,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a", AggregateState: v1alpha1.JobStateSuccess}, }, @@ -665,6 +1023,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a", AggregateState: v1alpha1.JobStateFailure}, }, @@ -684,6 +1043,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { payload: &v1alpha1.ReleasePayload{ Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, InformingJobResults: []v1alpha1.JobStatus{ {CIConfigurationName: "informing-a", AggregateState: v1alpha1.JobStateSuccess}, {CIConfigurationName: "informing-b", AggregateState: v1alpha1.JobStateFailure}, @@ -920,6 +1280,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -940,6 +1301,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -970,6 +1332,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -990,6 +1353,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -1020,6 +1384,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -1040,6 +1405,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -1070,6 +1436,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -1090,6 +1457,7 @@ func TestPayloadAcceptedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, diff --git a/pkg/cmd/release-payload-controller/payload_rejected_controller.go b/pkg/cmd/release-payload-controller/payload_rejected_controller.go index 89613633c..68bebed13 100644 --- a/pkg/cmd/release-payload-controller/payload_rejected_controller.go +++ b/pkg/cmd/release-payload-controller/payload_rejected_controller.go @@ -40,6 +40,7 @@ const ( // - .spec.payloadOverride.override // - .status.blockingJobResults // - .status.releaseCreationJobResult.status +// - .status.releaseMirrorJobResult.status // // and populates the following condition: // - .status.conditions.PayloadRejected @@ -140,6 +141,22 @@ func computeReleasePayloadRejectedCondition(payload *v1alpha1.ReleasePayload) me return rejectedCondition } + // When a mirror job is configured, it must reach a terminal state before + // rejection can be evaluated — including manual overrides, since the + // images need to be backed up before any decision is made. A failed + // mirror job does not cause rejection. + // Skip this gate if the payload has already been accepted or rejected — + // retroactively reverting a terminal release due to a stale mirror status + // would be disruptive for payloads that predate this check. + if payload.Spec.PayloadCreationConfig.ReleaseMirrorCoordinates.ReleaseMirrorJobName != "" && + !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadAccepted) && + !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadRejected) { + if payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobSuccess && + payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobFailed { + return rejectedCondition + } + } + // Check for PayloadOverride switch payload.Spec.PayloadOverride.Override { case v1alpha1.ReleasePayloadOverrideRejected: diff --git a/pkg/cmd/release-payload-controller/payload_rejected_controller_test.go b/pkg/cmd/release-payload-controller/payload_rejected_controller_test.go index 3f695abfd..677fc18b6 100644 --- a/pkg/cmd/release-payload-controller/payload_rejected_controller_test.go +++ b/pkg/cmd/release-payload-controller/payload_rejected_controller_test.go @@ -383,6 +383,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -403,6 +404,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -433,6 +435,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -453,6 +456,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -483,6 +487,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -503,6 +508,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -533,6 +539,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -553,6 +560,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -583,6 +591,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -603,6 +612,7 @@ func TestPayloadRejectedSync(t *testing.T) { }, Status: v1alpha1.ReleasePayloadStatus{ ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{Status: v1alpha1.ReleaseMirrorJobSuccess}, BlockingJobResults: []v1alpha1.JobStatus{ { AggregateState: v1alpha1.JobStateSuccess, @@ -700,3 +710,247 @@ func TestPayloadRejectedSync(t *testing.T) { }) } } + +func TestComputeReleasePayloadRejectedCondition(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + payload *v1alpha1.ReleasePayload + expected metav1.Condition + }{ + { + name: "MirrorJobUnsetBlocksRejection", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "MirrorJobUnknownBlocksRejection", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "MirrorJobFailedDoesNotCauseRejection", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobFailed, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionFalse, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "NoMirrorJobConfiguredSkipsGate", + payload: &v1alpha1.ReleasePayload{ + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "AlreadyRejectedSkipsMirrorGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "AlreadyRejectedWithUnsetMirrorSkipsGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "AlreadyAcceptedSkipsMirrorGateInRejectedController", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateSuccess}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + { + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionFalse, + Reason: ReleasePayloadRejectedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionFalse, + Reason: ReleasePayloadRejectedReason, + }, + }, + { + name: "NotYetRejectedStillBlockedByMirror", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "4.23.0-0.nightly-2026-07-14-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseCreationJobResult: v1alpha1.ReleaseCreationJobResult{Status: v1alpha1.ReleaseCreationJobSuccess}, + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + BlockingJobResults: []v1alpha1.JobStatus{ + {AggregateState: v1alpha1.JobStateFailure}, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadRejectedReason, + }, + }, + }, + }, + expected: metav1.Condition{ + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionUnknown, + Reason: ReleasePayloadRejectedReason, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := computeReleasePayloadRejectedCondition(tc.payload) + if diff := cmp.Diff(tc.expected, result, cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime")); diff != "" { + t.Errorf("unexpected condition (-expected +got):\n%s", diff) + } + }) + } +} From 2403af7574b00d45ea44dfb5abd0b7dac5eeedb9 Mon Sep 17 00:00:00 2001 From: Brad Williams Date: Tue, 14 Jul 2026 14:33:02 -0400 Subject: [PATCH 2/2] Code review fixes rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- .../payload_accepted_controller.go | 31 ++-- .../payload_accepted_controller_test.go | 169 +++++++++++++++++- .../payload_rejected_controller.go | 16 +- 3 files changed, 186 insertions(+), 30 deletions(-) diff --git a/pkg/cmd/release-payload-controller/payload_accepted_controller.go b/pkg/cmd/release-payload-controller/payload_accepted_controller.go index 9e0862eb6..b80aa35e0 100644 --- a/pkg/cmd/release-payload-controller/payload_accepted_controller.go +++ b/pkg/cmd/release-payload-controller/payload_accepted_controller.go @@ -138,20 +138,8 @@ func computeReleasePayloadAcceptedCondition(payload *v1alpha1.ReleasePayload) me return acceptedCondition } - // When a mirror job is configured, it must reach a terminal state before - // acceptance can be evaluated — including manual overrides, since the - // images need to be backed up before any decision is made. A failed - // mirror job does not block acceptance. - // Skip this gate if the payload has already been accepted or rejected — - // retroactively reverting a terminal release due to a stale mirror status - // would be disruptive for payloads that predate this check. - if payload.Spec.PayloadCreationConfig.ReleaseMirrorCoordinates.ReleaseMirrorJobName != "" && - !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadAccepted) && - !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadRejected) { - if payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobSuccess && - payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobFailed { - return acceptedCondition - } + if mirrorJobPending(payload) { + return acceptedCondition } // Check for "Accepted" PayloadOverride @@ -197,3 +185,18 @@ func computeReleasePayloadAcceptedCondition(payload *v1alpha1.ReleasePayload) me return acceptedCondition } + +// mirrorJobPending reports whether a configured mirror job has not yet reached +// a terminal state and the payload has not already been accepted or rejected. +// When true, acceptance/rejection evaluation should wait. +func mirrorJobPending(payload *v1alpha1.ReleasePayload) bool { + if payload.Spec.PayloadCreationConfig.ReleaseMirrorCoordinates.ReleaseMirrorJobName == "" { + return false + } + if v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadAccepted) || + v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadRejected) { + return false + } + return payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobSuccess && + payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobFailed +} diff --git a/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go b/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go index baa21516c..1cc1ea63b 100644 --- a/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go +++ b/pkg/cmd/release-payload-controller/payload_accepted_controller_test.go @@ -171,7 +171,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { Reason: ReleasePayloadAcceptedReason, }, }, - // === PayloadOverride (second precedence) === + // === PayloadOverride (third precedence — after mirror job) === { name: "OverrideAccepted", payload: &v1alpha1.ReleasePayload{ @@ -296,7 +296,7 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { Message: "Force reject despite success", }, }, - // === Mirror job (third precedence — after overrides) === + // === Mirror job (second precedence — blocks overrides until terminal) === { name: "MirrorJobUnsetBlocksAcceptance", payload: &v1alpha1.ReleasePayload{ @@ -1071,6 +1071,171 @@ func TestComputeReleasePayloadAcceptedCondition(t *testing.T) { } } +func TestMirrorJobPending(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + payload *v1alpha1.ReleasePayload + expected bool + }{ + { + name: "NoMirrorConfigured", + payload: &v1alpha1.ReleasePayload{}, + expected: false, + }, + { + name: "MirrorConfiguredStatusUnset", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + }, + expected: true, + }, + { + name: "MirrorConfiguredStatusUnknown", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + }, + }, + expected: true, + }, + { + name: "MirrorConfiguredStatusSuccess", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobSuccess, + }, + }, + }, + expected: false, + }, + { + name: "MirrorConfiguredStatusFailed", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobFailed, + }, + }, + }, + expected: false, + }, + { + name: "AlreadyAcceptedSkipsGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadAcceptedReason, + }, + }, + }, + }, + expected: false, + }, + { + name: "AlreadyRejectedSkipsGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadRejected, + Status: metav1.ConditionTrue, + Reason: ReleasePayloadRejectedReason, + }, + }, + }, + }, + expected: false, + }, + { + name: "ConditionFalseDoesNotSkipGate", + payload: &v1alpha1.ReleasePayload{ + Spec: v1alpha1.ReleasePayloadSpec{ + PayloadCreationConfig: v1alpha1.PayloadCreationConfig{ + ReleaseMirrorCoordinates: v1alpha1.ReleaseMirrorCoordinates{ + ReleaseMirrorJobName: "test-alternate-mirror", + }, + }, + }, + Status: v1alpha1.ReleasePayloadStatus{ + ReleaseMirrorJobResult: v1alpha1.ReleaseMirrorJobResult{ + Status: v1alpha1.ReleaseMirrorJobUnknown, + }, + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ConditionPayloadAccepted, + Status: metav1.ConditionFalse, + Reason: ReleasePayloadAcceptedReason, + }, + }, + }, + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := mirrorJobPending(tc.payload) + if result != tc.expected { + t.Errorf("expected %v, got %v", tc.expected, result) + } + }) + } +} func TestPayloadAcceptedSync(t *testing.T) { t.Parallel() diff --git a/pkg/cmd/release-payload-controller/payload_rejected_controller.go b/pkg/cmd/release-payload-controller/payload_rejected_controller.go index 68bebed13..31380bfc6 100644 --- a/pkg/cmd/release-payload-controller/payload_rejected_controller.go +++ b/pkg/cmd/release-payload-controller/payload_rejected_controller.go @@ -141,20 +141,8 @@ func computeReleasePayloadRejectedCondition(payload *v1alpha1.ReleasePayload) me return rejectedCondition } - // When a mirror job is configured, it must reach a terminal state before - // rejection can be evaluated — including manual overrides, since the - // images need to be backed up before any decision is made. A failed - // mirror job does not cause rejection. - // Skip this gate if the payload has already been accepted or rejected — - // retroactively reverting a terminal release due to a stale mirror status - // would be disruptive for payloads that predate this check. - if payload.Spec.PayloadCreationConfig.ReleaseMirrorCoordinates.ReleaseMirrorJobName != "" && - !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadAccepted) && - !v1helpers.IsConditionTrue(payload.Status.Conditions, v1alpha1.ConditionPayloadRejected) { - if payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobSuccess && - payload.Status.ReleaseMirrorJobResult.Status != v1alpha1.ReleaseMirrorJobFailed { - return rejectedCondition - } + if mirrorJobPending(payload) { + return rejectedCondition } // Check for PayloadOverride