Skip to content

Commit

Permalink
feat(RELEASE-1227): set application metadata in release controller
Browse files Browse the repository at this point in the history
This commit adds an operation to the release controller to set metadata
from the application on the release. It sets the ownerReference to be
the application as well as copies the pipelines as code and rhtapDomain
labels and annotations from the snapshot to the release. This is
currently done for automated releases by the integration service, so
this commit moves the logic to the release controller instead.

Signed-off-by: Johnny Bieren <[email protected]>
  • Loading branch information
johnbieren committed Jan 24, 2025
1 parent 66dc68b commit 82d64d4
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 10 deletions.
48 changes: 48 additions & 0 deletions controllers/release/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/go-logr/logr"
"github.com/konflux-ci/operator-toolkit/controller"
toolkitmetadata "github.com/konflux-ci/operator-toolkit/metadata"
"github.com/konflux-ci/release-service/api/v1alpha1"
"github.com/konflux-ci/release-service/loader"
"github.com/konflux-ci/release-service/metadata"
Expand Down Expand Up @@ -337,6 +338,53 @@ func (a *adapter) EnsureFinalPipelineIsProcessed() (controller.OperationResult,
return controller.ContinueProcessing()
}

// EnsureApplicationMetadataIsSet is an operation that will ensure that the owner reference is set
// to be the application the Release was created for and that all annotations and labels from the
// Snapshot pertaining to Pipelines as Code or the RhtapDomain prefix are copied to the Release.
func (a *adapter) EnsureApplicationMetadataIsSet() (controller.OperationResult, error) {
if len(a.release.OwnerReferences) > 0 {
return controller.ContinueProcessing()
}

releasePlan, err := a.loader.GetReleasePlan(a.ctx, a.client, a.release)
if err != nil {
return controller.RequeueWithError(err)
}

snapshot, err := a.loader.GetSnapshot(a.ctx, a.client, a.release)
if err != nil {
return controller.RequeueWithError(err)
}

patch := client.MergeFrom(a.release.DeepCopy())

application, err := a.loader.GetApplication(a.ctx, a.client, releasePlan)
if err != nil {
a.release.MarkReleaseFailed("This Release is for a nonexistent Application")
return controller.RequeueOnErrorOrStop(a.client.Status().Patch(a.ctx, a.release, patch))
}

err = ctrl.SetControllerReference(application, a.release, a.client.Scheme())
if err != nil {
return controller.RequeueWithError(err)
}

Check warning on line 370 in controllers/release/adapter.go

View check run for this annotation

Codecov / codecov/patch

controllers/release/adapter.go#L369-L370

Added lines #L369 - L370 were not covered by tests

// Propagate PaC annotations and labels
_ = toolkitmetadata.CopyAnnotationsByPrefix(&snapshot.ObjectMeta, &a.release.ObjectMeta, metadata.PipelinesAsCodePrefix)
_ = toolkitmetadata.CopyLabelsByPrefix(&snapshot.ObjectMeta, &a.release.ObjectMeta, metadata.PipelinesAsCodePrefix)

// Propagate annotations and labels prefixed with the RhtapDomain prefix
_ = toolkitmetadata.CopyAnnotationsByPrefix(&snapshot.ObjectMeta, &a.release.ObjectMeta, metadata.RhtapDomain)
_ = toolkitmetadata.CopyLabelsByPrefix(&snapshot.ObjectMeta, &a.release.ObjectMeta, metadata.RhtapDomain)

err = a.client.Patch(a.ctx, a.release, patch)
if err != nil && !errors.IsNotFound(err) {
return controller.RequeueWithError(err)
}

Check warning on line 383 in controllers/release/adapter.go

View check run for this annotation

Codecov / codecov/patch

controllers/release/adapter.go#L382-L383

Added lines #L382 - L383 were not covered by tests

return controller.ContinueProcessing()
}

// EnsureReleaseExpirationTimeIsAdded is an operation that ensures that a Release has the ExpirationTime set.
func (a *adapter) EnsureReleaseExpirationTimeIsAdded() (controller.OperationResult, error) {
if a.release.Status.ExpirationTime == nil {
Expand Down
121 changes: 121 additions & 0 deletions controllers/release/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,127 @@ var _ = Describe("Release adapter", Ordered, func() {

})

Context("When EnsureApplicationMetadataIsSet is called", func() {
var adapter *adapter

AfterEach(func() {
_ = adapter.client.Delete(ctx, adapter.release)
})

BeforeEach(func() {
adapter = createReleaseAndAdapter()
})

It("should do nothing if the Release already has an owner reference", func() {
adapter.release.OwnerReferences = []metav1.OwnerReference{
{Kind: "Application", Name: "foo"},
}

Expect(adapter.release.OwnerReferences).To(HaveLen(1))
result, err := adapter.EnsureApplicationMetadataIsSet()
Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue())
Expect(err).NotTo(HaveOccurred())
Expect(adapter.release.OwnerReferences).To(HaveLen(1))
})

It("should fail if the ReleasePlan does not exist", func() {
adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{
{
ContextKey: loader.ReleasePlanContextKey,
Err: errors.NewNotFound(schema.GroupResource{}, ""),
},
})

result, err := adapter.EnsureApplicationMetadataIsSet()
Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue())
Expect(err).To(HaveOccurred())
Expect(adapter.release.OwnerReferences).To(HaveLen(0))
})

It("should fail if the Snapshot does not exist", func() {
adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{
{
ContextKey: loader.SnapshotContextKey,
Err: errors.NewNotFound(schema.GroupResource{}, ""),
},
})

result, err := adapter.EnsureApplicationMetadataIsSet()
Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue())
Expect(err).To(HaveOccurred())
Expect(adapter.release.OwnerReferences).To(HaveLen(0))
})

It("should fail if the Application does not exist", func() {
adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{
{
ContextKey: loader.ApplicationContextKey,
Err: errors.NewNotFound(schema.GroupResource{}, ""),
},
})

result, err := adapter.EnsureApplicationMetadataIsSet()
Expect(!result.RequeueRequest && result.CancelRequest).To(BeTrue())
Expect(err).NotTo(HaveOccurred())
Expect(adapter.release.IsValid()).To(BeFalse())
Expect(adapter.release.OwnerReferences).To(HaveLen(0))
})

It("should set the owner reference", func() {
Expect(adapter.release.OwnerReferences).To(HaveLen(0))
result, err := adapter.EnsureApplicationMetadataIsSet()
Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue())
Expect(err).NotTo(HaveOccurred())

boolTrue := true
expectedOwnerReference := metav1.OwnerReference{
Kind: "Application",
APIVersion: "appstudio.redhat.com/v1alpha1",
UID: application.UID,
Name: application.Name,
Controller: &boolTrue,
BlockOwnerDeletion: &boolTrue,
}
Expect(adapter.release.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference))
})

It("should add the annotations and labels that have the proper prefix from the snapshot", func() {
metadataSnapshot := &applicationapiv1alpha1.Snapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "metadata-snapshot",
Namespace: "default",
Labels: map[string]string{
metadata.PipelinesAsCodePrefix + "/foo": "value",
metadata.RhtapDomain + "/bar": "value2",
"something-else": "value3",
},
Annotations: map[string]string{
metadata.PipelinesAsCodePrefix + "/test": "value",
metadata.RhtapDomain + "/baz": "value2",
"something-else-else": "value3",
},
},
Spec: applicationapiv1alpha1.SnapshotSpec{
Application: application.Name,
},
}
adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{
{
ContextKey: loader.SnapshotContextKey,
Resource: metadataSnapshot,
},
})
Expect(adapter.release.Labels).To(HaveLen(0))
Expect(adapter.release.Annotations).To(HaveLen(0))

result, err := adapter.EnsureApplicationMetadataIsSet()
Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue())
Expect(err).NotTo(HaveOccurred())
Expect(adapter.release.Labels).To(HaveLen(2))
Expect(adapter.release.Annotations).To(HaveLen(2))
})
})

When("EnsureReleaseExpirationTimeIsAdded is called", func() {
var adapter *adapter
var newReleasePlan *v1alpha1.ReleasePlan
Expand Down
1 change: 1 addition & 0 deletions controllers/release/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
adapter.EnsureConfigIsLoaded, // This operation sets the config in the adapter to be used in other operations.
adapter.EnsureReleaseIsRunning,
adapter.EnsureReleaseIsValid,
adapter.EnsureApplicationMetadataIsSet,

Check warning on line 82 in controllers/release/controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/release/controller.go#L82

Added line #L82 was not covered by tests
adapter.EnsureFinalizerIsAdded,
adapter.EnsureReleaseExpirationTimeIsAdded,
adapter.EnsureTenantPipelineIsProcessed,
Expand Down
26 changes: 16 additions & 10 deletions metadata/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,49 @@ import "fmt"
// Common constants
const (
// rhtapDomain is the prefix of the application label
rhtapDomain = "appstudio.openshift.io"
RhtapDomain = "appstudio.openshift.io"

// MaxLabelLength is the maximum allowed characters in a label value
MaxLabelLength = 63
)

// Prefixes used by the release controller package
var (
// PipelinesAsCodePrefix contains the prefix applied to labels and annotations copied from Pipelines as Code resources.
PipelinesAsCodePrefix = "pac.test.appstudio.openshift.io"
)

// Labels used by the release api package
var (
// AttributionLabel is the label name for the standing-attribution label
AttributionLabel = fmt.Sprintf("release.%s/standing-attribution", rhtapDomain)
AttributionLabel = fmt.Sprintf("release.%s/standing-attribution", RhtapDomain)

// AutoReleaseLabel is the label name for the auto-release setting
AutoReleaseLabel = fmt.Sprintf("release.%s/auto-release", rhtapDomain)
AutoReleaseLabel = fmt.Sprintf("release.%s/auto-release", RhtapDomain)

// AuthorLabel is the label name for the user who creates a CR
AuthorLabel = fmt.Sprintf("release.%s/author", rhtapDomain)
AuthorLabel = fmt.Sprintf("release.%s/author", RhtapDomain)

// AutomatedLabel is the label name for marking a Release as automated
AutomatedLabel = fmt.Sprintf("release.%s/automated", rhtapDomain)
AutomatedLabel = fmt.Sprintf("release.%s/automated", RhtapDomain)

// ReleasePlanAdmissionLabel is the ReleasePlan label for the name of the ReleasePlanAdmission to use
ReleasePlanAdmissionLabel = fmt.Sprintf("release.%s/releasePlanAdmission", rhtapDomain)
ReleasePlanAdmissionLabel = fmt.Sprintf("release.%s/releasePlanAdmission", RhtapDomain)
)

// Prefixes to be used by Release Pipelines labels
var (
// pipelinesLabelPrefix is the prefix of the pipelines label
pipelinesLabelPrefix = fmt.Sprintf("pipelines.%s", rhtapDomain)
pipelinesLabelPrefix = fmt.Sprintf("pipelines.%s", RhtapDomain)

// releaseLabelPrefix is the prefix of the release labels
releaseLabelPrefix = fmt.Sprintf("release.%s", rhtapDomain)
releaseLabelPrefix = fmt.Sprintf("release.%s", RhtapDomain)
)

// Labels to be used within Release PipelineRuns
var (
// ApplicationNameLabel is the label used to specify the application associated with the PipelineRun
ApplicationNameLabel = fmt.Sprintf("%s/%s", rhtapDomain, "application")
ApplicationNameLabel = fmt.Sprintf("%s/%s", RhtapDomain, "application")

// FinalPipelineType is the value to be used in the PipelinesTypeLabel for final Pipelines
FinalPipelineType = "final"
Expand All @@ -78,5 +84,5 @@ var (
ReleaseNamespaceLabel = fmt.Sprintf("%s/%s", releaseLabelPrefix, "namespace")

// ReleaseSnapshotLabel is the label used to specify the snapshot associated with the PipelineRun
ReleaseSnapshotLabel = fmt.Sprintf("%s/%s", rhtapDomain, "snapshot")
ReleaseSnapshotLabel = fmt.Sprintf("%s/%s", RhtapDomain, "snapshot")
)

0 comments on commit 82d64d4

Please sign in to comment.