From f05cd7d3e2badfff339a11fb547f78893112fc3f Mon Sep 17 00:00:00 2001 From: Deep Mistry Date: Fri, 5 Dec 2025 12:57:08 -0500 Subject: [PATCH] Updates garbage collection for orphaned ReleasePayloads to use coordinated deletion via remove__ prefixed tags in quay.io --- cmd/release-controller/sync_gc.go | 20 +++++++++++- cmd/release-controller/sync_release.go | 42 ++++++++++++++++++++++++++ pkg/release-controller/types.go | 2 +- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/cmd/release-controller/sync_gc.go b/cmd/release-controller/sync_gc.go index 0aa50e3a9..2c8426b9d 100644 --- a/cmd/release-controller/sync_gc.go +++ b/cmd/release-controller/sync_gc.go @@ -122,11 +122,29 @@ func (c *Controller) garbageCollectSync() error { } } - // all releasepayloads created for releases that no longer exist should be deleted + // all releasepayloads created for releases that no longer exist should be garbage collected + // Use coordinated deletion via remove__ prefixed tags in quay.io, allowing the pruner to handle tag deletion for _, payload := range payloads { if active.Has(payload.Name) { continue } + + // Get the release config from the ImageStream to check if alternate repository is configured + imageStream, err := c.releaseLister.ImageStreams(payload.Spec.PayloadCoordinates.Namespace).Get(payload.Spec.PayloadCoordinates.ImagestreamName) + if err == nil { + release, ok, err := releasecontroller.ReleaseDefinition(imageStream, c.parsedReleaseConfigCache, c.eventRecorder, *c.releaseLister) + if err == nil && ok && len(release.Config.AlternateImageRepository) > 0 && len(release.Config.AlternateImageRepositorySecretName) > 0 { + // Create a job to copy rc_payload__{version} to remove__rc_payload__{version} + _, err := c.ensureRemoveTagJob(payload, release) + if err != nil { + klog.V(2).Infof("Failed to create remove tag job for releasepayload %s/%s: %v, proceeding with direct deletion", payload.Namespace, payload.Name, err) + } else { + klog.V(2).Infof("Created remove tag job for orphaned releasepayload %s/%s, pruner will handle quay.io tag deletion", payload.Namespace, payload.Name) + } + } + } + + // Delete the ReleasePayload klog.V(2).Infof("Removing orphaned releasepayload %s/%s", payload.Namespace, payload.Name) if err := c.releasePayloadClient.ReleasePayloads(payload.Namespace).Delete(context.TODO(), payload.Name, metav1.DeleteOptions{}); err != nil && !errors.IsNotFound(err) { utilruntime.HandleError(fmt.Errorf("can't delete orphaned releasepayload %s/%s: %v", payload.Namespace, payload.Name, err)) diff --git a/cmd/release-controller/sync_release.go b/cmd/release-controller/sync_release.go index 6d654603c..d561497e6 100644 --- a/cmd/release-controller/sync_release.go +++ b/cmd/release-controller/sync_release.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/openshift/release-controller/pkg/apis/release/v1alpha1" releasecontroller "github.com/openshift/release-controller/pkg/release-controller" batchv1 "k8s.io/api/batch/v1" @@ -417,3 +418,44 @@ func (c *Controller) ensureReleaseMirrorJob(release *releasecontroller.Release, func releaseMirrorJobName(tagName string) string { return fmt.Sprintf("%s-alternate-mirror", tagName) } + +// ensureRemoveTagJob creates a job to copy rc_payload__{version} to remove__rc_payload__{version} in quay.io +func (c *Controller) ensureRemoveTagJob(payload *v1alpha1.ReleasePayload, release *releasecontroller.Release) (*batchv1.Job, error) { + if len(release.Config.AlternateImageRepository) == 0 || len(release.Config.AlternateImageRepositorySecretName) == 0 { + return nil, fmt.Errorf("alternate repository or secret not configured") + } + + jobName := fmt.Sprintf("%s-remove-tag", payload.Name) + return c.ensureJob(jobName, nil, func() (*batchv1.Job, error) { + // Get cli image from mirror or config + cliImage := "registry.ci.openshift.org/ocp/4.16:cli" + if mirror, err := c.releaseLister.ImageStreams(release.Target.Namespace).Get(release.Target.Name); err == nil { + cliImage = fmt.Sprintf("%s:cli", mirror.Status.DockerImageRepository) + } + if len(release.Config.OverrideCLIImage) > 0 { + cliImage = release.Config.OverrideCLIImage + } + + job, prefix := newReleaseJobBase(jobName, cliImage, release.Config.AlternateImageRepositorySecretName) + + rcPayloadTag := fmt.Sprintf("rc_payload__%s", payload.Name) + removeTag := fmt.Sprintf("remove__rc_payload__%s", payload.Name) + fromImage := fmt.Sprintf("%s:%s", release.Config.AlternateImageRepository, rcPayloadTag) + toImage := fmt.Sprintf("%s:%s", release.Config.AlternateImageRepository, removeTag) + + job.Spec.Template.Spec.Containers[0].Command = []string{ + "/bin/bash", "-c", + prefix + ` + oc image mirror --keep-manifest-list=true $1 $2 + `, + "", + fromImage, toImage, + } + + job.Annotations[releasecontroller.ReleaseAnnotationReleaseTag] = payload.Name + job.Annotations[releasecontroller.ReleaseAnnotationTarget] = fmt.Sprintf("%s/%s", payload.Spec.PayloadCoordinates.Namespace, payload.Spec.PayloadCoordinates.ImagestreamName) + + klog.V(2).Infof("Creating remove tag job %s/%s to copy %s to %s", c.jobNamespace, job.Name, fromImage, toImage) + return job, nil + }) +} diff --git a/pkg/release-controller/types.go b/pkg/release-controller/types.go index 287421a7a..eae6969a9 100644 --- a/pkg/release-controller/types.go +++ b/pkg/release-controller/types.go @@ -152,7 +152,7 @@ type ReleaseConfig struct { // AlternateImageRepository is the full path to an external Image Repository where we // will mirror Accepted releases to. // For example: - // "alternateImageRepository": "quay.io/openshift-release-dev/dev-release" + // "alternateImageRepository": "quay.io/openshift/ci" AlternateImageRepository string `json:"alternateImageRepository"` // AlternateImageRepositorySecret is the name of the secret containing credentials to the