-
Notifications
You must be signed in to change notification settings - Fork 11
feat(plugindefinitions): enable OCI chart replication for ClusterPluginDefinition #1956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mikolajkucinski
merged 6 commits into
main
from
feat/cluster-plugindefinition-chart-replication
May 7, 2026
+232
β11
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
0d2e193
feat(plugindefinitions): enable OCI chart replication for ClusterPlugβ¦
mikolajkucinski e53c071
test(e2e): add OCI chart replication suite for ClusterPluginDefinition
mikolajkucinski 5ad9009
refactor(plugindefinitions): clean up cluster reconciler RBAC and e2eβ¦
mikolajkucinski 9113774
test(e2e): teardown OCI mirroring after cluster plugindefinition suite
mikolajkucinski cfcb880
test(e2e): apply greenhouse organization for OCI mirror config
mikolajkucinski 3b39b0a
ci(e2e): run nightly e2e every 2 hours
mikolajkucinski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Greenhouse contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //go:build clusterplugindefinitionE2E | ||
|
|
||
| package clusterplugindefinition | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| logf "sigs.k8s.io/controller-runtime/pkg/log" | ||
| "sigs.k8s.io/controller-runtime/pkg/log/zap" | ||
|
|
||
| "github.com/cloudoperators/greenhouse/e2e/clusterplugindefinition/scenarios" | ||
| "github.com/cloudoperators/greenhouse/e2e/shared" | ||
| "github.com/cloudoperators/greenhouse/internal/clientutil" | ||
| ) | ||
|
|
||
| var ( | ||
| env *shared.TestEnv | ||
| ctx context.Context | ||
| adminClient client.Client | ||
| testStartTime time.Time | ||
| ) | ||
|
|
||
| func TestE2e(t *testing.T) { | ||
| RegisterFailHandler(Fail) | ||
| RunSpecs(t, "ClusterPluginDefinition E2E Suite") | ||
| } | ||
|
|
||
| var _ = BeforeSuite(func() { | ||
| logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) | ||
| ctx = context.Background() | ||
| env = shared.NewExecutionEnv() | ||
|
|
||
| var err error | ||
| adminClient, err = clientutil.NewK8sClientFromRestClientGetter(env.AdminRestClientGetter) | ||
| Expect(err).ToNot(HaveOccurred(), "there should be no error creating the admin client") | ||
|
|
||
| env = env.WithOrganization(ctx, adminClient, "./testdata/organization.yaml") | ||
| shared.SetupOCIMirroringForOrg(ctx, adminClient, env.TestNamespace) | ||
|
|
||
| testStartTime = time.Now().UTC() | ||
| }) | ||
|
|
||
| var _ = AfterSuite(func() { | ||
| env.GenerateGreenhouseControllerLogs(ctx, testStartTime) | ||
| env.GenerateFluxControllerLogs(ctx, "helm-controller", testStartTime) | ||
| shared.TeardownOCIMirroringForOrg(ctx, adminClient, env.TestNamespace) | ||
| }) | ||
|
|
||
| var _ = Describe("ClusterPluginDefinition E2E", Ordered, func() { | ||
| It("should replicate helm chart to registry mirror", func() { | ||
| scenarios.ClusterPDChartReplication(ctx, adminClient) | ||
| }) | ||
|
|
||
| It("should fail chart replication for non-existent chart version", func() { | ||
| scenarios.ClusterPDChartReplicationFailure(ctx, adminClient) | ||
| }) | ||
| }) | ||
124 changes: 124 additions & 0 deletions
124
e2e/clusterplugindefinition/scenarios/cluster_pd_chart_replication.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| // SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Greenhouse contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package scenarios | ||
|
|
||
| import ( | ||
| "context" | ||
| "time" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
|
|
||
| greenhousev1alpha1 "github.com/cloudoperators/greenhouse/api/v1alpha1" | ||
| "github.com/cloudoperators/greenhouse/e2e/shared" | ||
| "github.com/cloudoperators/greenhouse/internal/test" | ||
| "github.com/cloudoperators/greenhouse/pkg/lifecycle" | ||
| ) | ||
|
|
||
| // ClusterPDChartReplication verifies that the ClusterPluginDefinition controller replicates a Helm chart OCI artifact via the registry mirror configured on the greenhouse Organization. | ||
| func ClusterPDChartReplication(ctx context.Context, adminClient client.Client) { | ||
| cpd := test.NewClusterPluginDefinition(ctx, "podinfo-cluster-chart-replication", | ||
| test.WithVersion("6.7.1"), | ||
| test.WithHelmChart(&greenhousev1alpha1.HelmChartReference{ | ||
| Name: "podinfo", | ||
| Repository: "oci://registry:5000/greenhouse-ghcr-io-mirror/stefanprodan/charts", | ||
| Version: "6.7.1", | ||
| }), | ||
| ) | ||
|
|
||
| By("creating ClusterPluginDefinition with chart URL at registry mirror") | ||
| err := adminClient.Create(ctx, cpd) | ||
| Expect(client.IgnoreAlreadyExists(err)).ToNot(HaveOccurred()) | ||
| DeferCleanup(func() { test.EventuallyDeleted(ctx, adminClient, cpd) }) | ||
|
|
||
| By("verifying OCIReplicationReady condition becomes True with OCIReplicationSucceeded") | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| cond := cpd.Status.StatusConditions.GetConditionByType(greenhousev1alpha1.OCIReplicationReadyCondition) | ||
| g.Expect(cond).NotTo(BeNil(), "OCIReplicationReady condition should be set") | ||
| g.Expect(cond.IsTrue()).To(BeTrue(), "chart replication should succeed") | ||
| g.Expect(cond.Reason).To(Equal(greenhousev1alpha1.OCIReplicationSucceededReason)) | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed(), "registry mirror should pull-through the podinfo chart from ghcr.io") | ||
|
|
||
| By("verifying LastSyncedArtifact is populated") | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| artifact := cpd.GetLastSyncedArtifact() | ||
| g.Expect(artifact).NotTo(BeNil(), "LastSyncedArtifact should be set after replication") | ||
| g.Expect(artifact.Registry).To(Equal("registry:5000")) | ||
| g.Expect(artifact.ChartName).To(Equal("greenhouse-ghcr-io-mirror/stefanprodan/charts/podinfo")) | ||
| g.Expect(artifact.Version).To(Equal("6.7.1")) | ||
| g.Expect(artifact.Digest).NotTo(BeEmpty(), "digest should be populated after successful replication") | ||
| g.Expect(artifact.ReplicationStatus).To(Equal(greenhousev1alpha1.ReplicationStatusReplicated)) | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed()) | ||
|
|
||
| By("verifying HelmChartReady condition becomes True") | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| cond := cpd.Status.StatusConditions.GetConditionByType(greenhousev1alpha1.HelmChartReadyCondition) | ||
| g.Expect(cond).NotTo(BeNil()) | ||
| g.Expect(cond.IsTrue()).To(BeTrue(), "Flux should fetch the chart from the registry mirror over HTTP") | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed()) | ||
|
|
||
| By("verifying ClusterPluginDefinition is Ready") | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| g.Expect(cpd.Status.IsReadyTrue()).To(BeTrue()) | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed()) | ||
|
|
||
| By("verifying chart replication is idempotent on re-reconciliation") | ||
| Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| originalDigest := cpd.GetLastSyncedArtifact().Digest | ||
|
|
||
| ann := cpd.GetAnnotations() | ||
| if ann == nil { | ||
| ann = make(map[string]string) | ||
| } | ||
| ann[lifecycle.ReconcileAnnotation] = time.Now().UTC().Format(time.RFC3339Nano) | ||
| cpd.SetAnnotations(ann) | ||
| Expect(adminClient.Update(ctx, cpd)).To(Succeed()) | ||
|
|
||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| cond := cpd.Status.StatusConditions.GetConditionByType(greenhousev1alpha1.OCIReplicationReadyCondition) | ||
| g.Expect(cond).NotTo(BeNil()) | ||
| g.Expect(cond.IsTrue()).To(BeTrue()) | ||
| artifact := cpd.GetLastSyncedArtifact() | ||
| g.Expect(artifact).NotTo(BeNil()) | ||
| g.Expect(artifact.Digest).To(Equal(originalDigest), "re-reconciliation must not change the replicated artifact digest") | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed()) | ||
| } | ||
|
|
||
| // ClusterPDChartReplicationFailure verifies that the ClusterPluginDefinition controller sets OCIReplicationFailed for a non-existent chart version. | ||
| func ClusterPDChartReplicationFailure(ctx context.Context, adminClient client.Client) { | ||
| cpd := test.NewClusterPluginDefinition(ctx, "podinfo-cluster-chart-failure", | ||
| test.WithVersion("99.99.99"), | ||
| test.WithHelmChart(&greenhousev1alpha1.HelmChartReference{ | ||
| Name: "podinfo", | ||
| Repository: "oci://registry:5000/greenhouse-ghcr-io-mirror/stefanprodan/charts", | ||
| Version: "99.99.99", | ||
| }), | ||
| ) | ||
|
|
||
| By("creating ClusterPluginDefinition with non-existent chart version") | ||
| err := adminClient.Create(ctx, cpd) | ||
| Expect(client.IgnoreAlreadyExists(err)).ToNot(HaveOccurred()) | ||
| DeferCleanup(func() { test.EventuallyDeleted(ctx, adminClient, cpd) }) | ||
|
|
||
| By("verifying OCIReplicationReady condition becomes False with OCIReplicationFailed reason") | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| cond := cpd.Status.StatusConditions.GetConditionByType(greenhousev1alpha1.OCIReplicationReadyCondition) | ||
| g.Expect(cond).NotTo(BeNil()) | ||
| g.Expect(cond.IsTrue()).To(BeFalse()) | ||
| g.Expect(cond.Reason).To(Equal(greenhousev1alpha1.OCIReplicationFailedReason)) | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed(), "registry mirror pull-through should fail for a non-existent chart version") | ||
|
|
||
| By("verifying ClusterPluginDefinition is not Ready") | ||
| Eventually(func(g Gomega) { | ||
| g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cpd), cpd)).To(Succeed()) | ||
| g.Expect(cpd.Status.IsReadyTrue()).To(BeFalse()) | ||
| }).WithTimeout(shared.OCIReplicationTimeout).Should(Succeed()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Greenhouse contributors | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| apiVersion: greenhouse.sap/v1alpha1 | ||
| kind: Organization | ||
| metadata: | ||
| name: greenhouse | ||
| spec: | ||
| description: greenhouse organization | ||
| displayName: Greenhouse | ||
| mappedOrgAdminIdPGroup: GREENHOUSE_ORG_ADMIN |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Greenhouse contributors | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # Cluster Scoped Resource | ||
| apiVersion: greenhouse.sap/v1alpha1 | ||
| kind: Organization | ||
| metadata: | ||
| name: greenhouse | ||
| spec: | ||
| description: greenhouse organization for cluster-scoped plugin definitions | ||
| displayName: Greenhouse | ||
| mappedOrgAdminIdPGroup: GREENHOUSE_ORG_ADMIN |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.