Skip to content

Commit

Permalink
feat(RELEASE-913): add GetPreviousRelease loader function
Browse files Browse the repository at this point in the history
A new funciton has been added to the loader to be able to get the
Release created just before a given Release. This is a key function
for the collectors feature.

Signed-off-by: David Moreno García <[email protected]>
  • Loading branch information
davidmogar committed Aug 26, 2024
1 parent b2b2787 commit 64d5390
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 0 deletions.
10 changes: 10 additions & 0 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ func SetupComponentCache(mgr ctrl.Manager) error {
"spec.application", componentIndexFunc)
}

// SetupReleaseCache adds a new index field to be able to search Releases by ReleasePlan name.
func SetupReleaseCache(mgr ctrl.Manager) error {
releaseIndexFunc := func(obj client.Object) []string {
return []string{obj.(*v1alpha1.Release).Spec.ReleasePlan}
}

return mgr.GetCache().IndexField(context.Background(), &v1alpha1.Release{},
"spec.releasePlan", releaseIndexFunc)
}

// SetupReleasePlanCache adds a new index field to be able to search ReleasePlans by target.
func SetupReleasePlanCache(mgr ctrl.Manager) error {
releasePlanIndexFunc := func(obj client.Object) []string {
Expand Down
3 changes: 3 additions & 0 deletions controllers/release/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ func (c *Controller) SetupCache(mgr ctrl.Manager) error {
if err := cache.SetupComponentCache(mgr); err != nil {
return err
}
if err := cache.SetupReleaseCache(mgr); err != nil {
return err
}

// NOTE: Both the release and releaseplan controller need this ReleasePlanAdmission cache. However, it only needs to be added
// once to the manager, so only one controller should add it. If it is removed here, it should be added to the ReleasePlan controller.
Expand Down
39 changes: 39 additions & 0 deletions loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package loader
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"os"
"strings"

Expand All @@ -29,6 +31,7 @@ type ObjectLoader interface {
GetEnterpriseContractPolicy(ctx context.Context, cli client.Client, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*ecapiv1alpha1.EnterpriseContractPolicy, error)
GetMatchingReleasePlanAdmission(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*v1alpha1.ReleasePlanAdmission, error)
GetMatchingReleasePlans(ctx context.Context, cli client.Client, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*v1alpha1.ReleasePlanList, error)
GetPreviousRelease(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.Release, error)
GetRelease(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.Release, error)
GetRoleBindingFromReleaseStatus(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*rbac.RoleBinding, error)
GetReleasePipelineRun(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*tektonv1.PipelineRun, error)
Expand Down Expand Up @@ -170,6 +173,42 @@ func (l *loader) GetMatchingReleasePlans(ctx context.Context, cli client.Client,
return releasePlans, nil
}

// GetPreviousRelease returns the Release that was created just before the given Release.
// If no previous Release is found, a NotFound error is returned.
func (l *loader) GetPreviousRelease(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.Release, error) {
releases := &v1alpha1.ReleaseList{}
err := cli.List(ctx, releases,
client.InNamespace(release.Namespace),
client.MatchingFields{"spec.releasePlan": release.Spec.ReleasePlan})
if err != nil {
return nil, err
}

var previousRelease *v1alpha1.Release

// Find the previous release
for i, possiblePreviousRelease := range releases.Items {
// Ignore the release passed as argument and any release created after that one
if possiblePreviousRelease.Name == release.Name ||
possiblePreviousRelease.CreationTimestamp.After(release.CreationTimestamp.Time) {
continue
}
if previousRelease == nil || possiblePreviousRelease.CreationTimestamp.After(previousRelease.CreationTimestamp.Time) {
previousRelease = &releases.Items[i]
}
}

if previousRelease == nil {
return nil, errors.NewNotFound(
schema.GroupResource{
Group: v1alpha1.GroupVersion.Group,
Resource: release.GetObjectKind().GroupVersionKind().Kind,
}, release.Name)
}

return previousRelease, nil
}

// GetRelease returns the Release with the given name and namespace. If the Release is not found or the Get operation
// fails, an error will be returned.
func (l *loader) GetRelease(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.Release, error) {
Expand Down
9 changes: 9 additions & 0 deletions loader/loader_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
EnterpriseContractPolicyContextKey
MatchedReleasePlansContextKey
MatchedReleasePlanAdmissionContextKey
PreviousReleaseContextKey
ProcessingResourcesContextKey
ReleaseContextKey
ReleasePipelineRunContextKey
Expand Down Expand Up @@ -97,6 +98,14 @@ func (l *mockLoader) GetMatchingReleasePlans(ctx context.Context, cli client.Cli
return toolkit.GetMockedResourceAndErrorFromContext(ctx, MatchedReleasePlansContextKey, &v1alpha1.ReleasePlanList{})
}

// GetPreviousRelease returns the resource and error passed as values of the context.
func (l *mockLoader) GetPreviousRelease(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.Release, error) {
if ctx.Value(PreviousReleaseContextKey) == nil {
return l.loader.GetPreviousRelease(ctx, cli, release)
}
return toolkit.GetMockedResourceAndErrorFromContext(ctx, PreviousReleaseContextKey, &v1alpha1.Release{})
}

// GetRelease returns the resource and error passed as values of the context.
func (l *mockLoader) GetRelease(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.Release, error) {
if ctx.Value(ReleaseContextKey) == nil {
Expand Down
15 changes: 15 additions & 0 deletions loader/loader_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ var _ = Describe("Release Adapter", Ordered, func() {
})
})

When("calling GetPreviousRelease", func() {
It("returns the resource and error from the context", func() {
release := &v1alpha1.Release{}
mockContext := toolkit.GetMockedContext(ctx, []toolkit.MockData{
{
ContextKey: PreviousReleaseContextKey,
Resource: release,
},
})
resource, err := loader.GetPreviousRelease(mockContext, nil, release)
Expect(resource).To(Equal(release))
Expect(err).To(BeNil())
})
})

When("calling GetRelease", func() {
It("returns the resource and error from the context", func() {
release := &v1alpha1.Release{}
Expand Down
79 changes: 79 additions & 0 deletions loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"strings"
"time"

tektonutils "github.com/konflux-ci/release-service/tekton/utils"

Expand All @@ -20,6 +21,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("Release Adapter", Ordered, func() {
Expand Down Expand Up @@ -238,6 +240,83 @@ var _ = Describe("Release Adapter", Ordered, func() {
})
})

When("calling GetPreviousRelease", func() {
var newerRelease, mostRecentRelease *v1alpha1.Release

AfterEach(func() {
k8sClient.Delete(ctx, newerRelease)
k8sClient.Delete(ctx, mostRecentRelease)

// Wait until the releases are gone
Eventually(func() bool {
releases := &v1alpha1.ReleaseList{}
err := k8sClient.List(ctx, releases,
client.InNamespace(release.Namespace),
client.MatchingFields{"spec.releasePlan": release.Spec.ReleasePlan})
return err == nil && len(releases.Items) == 1
}).Should(BeTrue())
})

It("returns a NotFound error if no previous release is found", func() {
returnedObject, err := loader.GetPreviousRelease(ctx, k8sClient, release)
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())
Expect(returnedObject).To(BeNil())
})

It("returns the previous release if found", func() {
// We need a new release with a more recent creation timestamp
time.Sleep(1 * time.Second)

newerRelease = release.DeepCopy()
newerRelease.Name = "newer-release"
newerRelease.ResourceVersion = ""
Expect(k8sClient.Create(ctx, newerRelease)).To(Succeed())

// Wait until the new release is cached
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: newerRelease.Name, Namespace: newerRelease.Namespace}, newerRelease)
}).Should(Succeed())

returnedObject, err := loader.GetPreviousRelease(ctx, k8sClient, newerRelease)
Expect(err).ToNot(HaveOccurred())
Expect(returnedObject).ToNot(BeNil())
Expect(returnedObject.Name).To(Equal(release.Name))
})

It("returns the previous release if multiple releases are found", func() {
// We need two new releases with a more recent creation timestamp
time.Sleep(1 * time.Second)

newerRelease = release.DeepCopy()
newerRelease.Name = "newer-release"
newerRelease.ResourceVersion = ""
Expect(k8sClient.Create(ctx, newerRelease)).To(Succeed())

// Wait until the new release is cached
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: newerRelease.Name, Namespace: newerRelease.Namespace}, newerRelease)
}).Should(Succeed())

time.Sleep(1 * time.Second)

mostRecentRelease = release.DeepCopy()
mostRecentRelease.Name = "most-recent-release"
mostRecentRelease.ResourceVersion = ""
Expect(k8sClient.Create(ctx, mostRecentRelease)).To(Succeed())

// Wait until the new release is cached
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: mostRecentRelease.Name, Namespace: mostRecentRelease.Namespace}, mostRecentRelease)
}).Should(Succeed())

returnedObject, err := loader.GetPreviousRelease(ctx, k8sClient, mostRecentRelease)
Expect(err).ToNot(HaveOccurred())
Expect(returnedObject).ToNot(BeNil())
Expect(returnedObject.Name).To(Equal(newerRelease.Name))
})
})

When("calling GetRelease", func() {
It("returns the requested release", func() {
returnedObject, err := loader.GetRelease(ctx, k8sClient, release.Name, release.Namespace)
Expand Down
1 change: 1 addition & 0 deletions loader/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ var _ = BeforeSuite(func() {
defer GinkgoRecover()

Expect(cache.SetupComponentCache(mgr)).To(Succeed())
Expect(cache.SetupReleaseCache(mgr)).To(Succeed())
Expect(cache.SetupReleasePlanCache(mgr)).To(Succeed())
Expect(cache.SetupReleasePlanAdmissionCache(mgr)).To(Succeed())

Expand Down

0 comments on commit 64d5390

Please sign in to comment.