From 0606cd29986c5e8f448725915d5f880918ce4995 Mon Sep 17 00:00:00 2001
From: Soule BA
Date: Mon, 11 Mar 2024 18:04:46 +0100
Subject: [PATCH] Adding additional tests for ociRepository chartRef
Signed-off-by: Soule BA
---
.github/workflows/e2e.yaml | 5 +
api/v2beta2/reference_types.go | 2 +-
.../helm.toolkit.fluxcd.io_helmreleases.yaml | 1 -
config/rbac/role.yaml | 14 +
.../install-from-ocirepo-source/test.yaml | 34 ++
docs/api/v2beta2/helm.md | 16 +-
internal/controller/helmrelease_controller.go | 20 +-
.../controller/helmrelease_controller_test.go | 398 +++++++++++++++++-
8 files changed, 472 insertions(+), 18 deletions(-)
create mode 100644 config/testdata/install-from-ocirepo-source/test.yaml
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 81c7ecf76..443c61ead 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -138,6 +138,11 @@ jobs:
kubectl -n install-create-target-ns get deployment install-create-target-ns-install-create-target-ns-podinfo
kubectl -n helm-system delete -f config/testdata/install-create-target-ns
+ - name: Run install from ocirepo test
+ run: |
+ kubectl -n helm-system apply -f config/testdata/install-from-ocirepo-source
+ kubectl -n helm-system wait helmreleases/podinfo-from-ocirepo --for=condition=ready --timeout=4m
+ kubectl -n helm-system delete -f config/testdata/install-from-ocirepo-source
- name: Run install fail test
run: |
test_name=install-fail
diff --git a/api/v2beta2/reference_types.go b/api/v2beta2/reference_types.go
index 43b722703..6208b2207 100644
--- a/api/v2beta2/reference_types.go
+++ b/api/v2beta2/reference_types.go
@@ -50,7 +50,7 @@ type CrossNamespaceSourceReference struct {
APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent.
- // +kubebuilder:validation:Enum=OCIRepository;HelmChart
+ // +kubebuilder:validation:Enum=OCIRepository
// +required
Kind string `json:"kind"`
diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
index 05f253caa..afc1af172 100644
--- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
+++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
@@ -1430,7 +1430,6 @@ spec:
description: Kind of the referent.
enum:
- OCIRepository
- - HelmChart
type: string
name:
description: Name of the referent.
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 954df8e75..52b74ee1f 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -55,3 +55,17 @@ rules:
- helmcharts/status
verbs:
- get
+- apiGroups:
+ - source.toolkit.fluxcd.io
+ resources:
+ - ocirepositories
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - source.toolkit.fluxcd.io
+ resources:
+ - ocirepositories/status
+ verbs:
+ - get
diff --git a/config/testdata/install-from-ocirepo-source/test.yaml b/config/testdata/install-from-ocirepo-source/test.yaml
new file mode 100644
index 000000000..b2ada044e
--- /dev/null
+++ b/config/testdata/install-from-ocirepo-source/test.yaml
@@ -0,0 +1,34 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name: podinfo-ocirepo
+spec:
+ interval: 10m
+ url: oci://ghcr.io/stefanprodan/charts/podinfo
+ ref:
+ tag: 6.6.0
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta2
+kind: HelmRelease
+metadata:
+ name: podinfo-from-ocirepo
+spec:
+ releaseName: podinfo
+ chartRef:
+ kind: OCIRepository
+ name: podinfo-ocirepo
+ interval: 50m
+ driftDetection:
+ mode: enabled
+ install:
+ timeout: 10m
+ remediation:
+ retries: 3
+ upgrade:
+ timeout: 10m
+ values:
+ resources:
+ requests:
+ cpu: 100m
+ memory: 64Mi
diff --git a/docs/api/v2beta2/helm.md b/docs/api/v2beta2/helm.md
index 9473052d0..e59442f9a 100644
--- a/docs/api/v2beta2/helm.md
+++ b/docs/api/v2beta2/helm.md
@@ -618,6 +618,10 @@ handle differences between the manifest in the Helm storage and the resources
currently existing in the cluster.
Filter holds the configuration for individual Helm test filters.
+
+(Appears on:
+Snapshot)
+
TestHookStatus holds the status information for a test hook as observed
to be run by the controller.
diff --git a/internal/controller/helmrelease_controller.go b/internal/controller/helmrelease_controller.go
index be3ac103f..8c1109139 100644
--- a/internal/controller/helmrelease_controller.go
+++ b/internal/controller/helmrelease_controller.go
@@ -74,6 +74,8 @@ import (
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases/finalizers,verbs=get;create;update;patch;delete
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts,verbs=get;list;watch
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/status,verbs=get
+// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories,verbs=get;list;watch
+// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories/status,verbs=get
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
// HelmReleaseReconciler reconciles a HelmRelease object.
@@ -155,7 +157,7 @@ func (r *HelmReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
if !isValidChartRef(obj) {
- return ctrl.Result{}, reconcile.TerminalError(fmt.Errorf("invalid HelmChart reference"))
+ return ctrl.Result{}, reconcile.TerminalError(fmt.Errorf("invalid Chart reference"))
}
// Initialize the patch helper with the current version of the object.
@@ -277,7 +279,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
return ctrl.Result{}, reconcile.TerminalError(err)
}
- msg := fmt.Sprintf("could not get HelmChart object: %s", err.Error())
+ msg := fmt.Sprintf("could not get Source object: %s", err.Error())
conditions.MarkFalse(obj, meta.ReadyCondition, v2.ArtifactFailedReason, msg)
return ctrl.Result{}, err
}
@@ -289,13 +291,13 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
// Check if the source is ready.
if ready, msg := isSourceReady(source); !ready {
log.Info(msg)
- conditions.MarkFalse(obj, meta.ReadyCondition, "HelmChartNotReady", msg)
+ conditions.MarkFalse(obj, meta.ReadyCondition, "SourceNotReady", msg)
// Do not requeue immediately, when the artifact is created
// the watcher should trigger a reconciliation.
return jitter.JitteredRequeueInterval(ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}), errWaitForChart
}
// Remove any stale corresponding Ready=False condition with Unknown.
- if conditions.HasAnyReason(obj, meta.ReadyCondition, "HelmChartNotReady") {
+ if conditions.HasAnyReason(obj, meta.ReadyCondition, "SourceNotReady") {
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
}
@@ -315,7 +317,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
loadedChart, err := loader.SecureLoadChartFromURL(loader.NewRetryableHTTPClient(ctx, r.artifactFetchRetries), source.GetArtifact().URL, source.GetArtifact().Digest)
if err != nil {
if errors.Is(err, loader.ErrFileNotFound) {
- msg := fmt.Sprintf("Chart not ready: artifact not found. Retrying in %s", r.requeueDependency.String())
+ msg := fmt.Sprintf("Source not ready: artifact not found. Retrying in %s", r.requeueDependency.String())
conditions.MarkFalse(obj, meta.ReadyCondition, v2.ArtifactFailedReason, msg)
log.Info(msg)
return ctrl.Result{RequeueAfter: r.requeueDependency}, errWaitForDependency
@@ -678,6 +680,9 @@ func (r *HelmReleaseReconciler) getSource(ctx context.Context, obj *v2.HelmRelea
return r.getHelmChartFromOCIRef(ctx, obj)
}
name, namespace = obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace
+ if namespace == "" {
+ namespace = obj.GetNamespace()
+ }
} else {
namespace, name = obj.Status.GetHelmChart()
}
@@ -696,7 +701,10 @@ func (r *HelmReleaseReconciler) getSource(ctx context.Context, obj *v2.HelmRelea
}
func (r *HelmReleaseReconciler) getHelmChartFromOCIRef(ctx context.Context, obj *v2.HelmRelease) (source.Source, error) {
- namespace, name := obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace
+ name, namespace := obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace
+ if namespace == "" {
+ namespace = obj.GetNamespace()
+ }
ociRepoRef := types.NamespacedName{Namespace: namespace, Name: name}
if err := intacl.AllowsAccessTo(obj, sourcev1.OCIRepositoryKind, ociRepoRef); err != nil {
diff --git a/internal/controller/helmrelease_controller_test.go b/internal/controller/helmrelease_controller_test.go
index a2179739d..4f729599d 100644
--- a/internal/controller/helmrelease_controller_test.go
+++ b/internal/controller/helmrelease_controller_test.go
@@ -143,7 +143,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"),
- *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get HelmChart object"),
+ *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get Source object"),
}))
})
@@ -228,7 +228,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
- *conditions.FalseCondition(meta.ReadyCondition, "HelmChartNotReady", "HelmChart 'mock/chart' is not ready"),
+ *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "HelmChart 'mock/chart' is not ready"),
}))
})
@@ -280,7 +280,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
- *conditions.FalseCondition(meta.ReadyCondition, "HelmChartNotReady", "HelmChart 'mock/chart' is not ready"),
+ *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "HelmChart 'mock/chart' is not ready"),
}))
})
@@ -332,7 +332,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
- *conditions.FalseCondition(meta.ReadyCondition, "HelmChartNotReady", "HelmChart 'mock/chart' is not ready"),
+ *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "HelmChart 'mock/chart' is not ready"),
}))
})
@@ -444,7 +444,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
- *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Chart not ready"),
+ *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"),
}))
})
@@ -862,7 +862,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
v2.DependencyNotReadyReason,
aclv1.AccessDeniedReason,
v2.ArtifactFailedReason,
- "HelmChartNotReady",
+ "SourceNotReady",
"ValuesError",
"RESTClientError",
"FactoryError",
@@ -888,6 +888,390 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
})
}
+func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testing.T) {
+ t.Run("handles ChartRef get failure", func(t *testing.T) {
+ g := NewWithT(t)
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock",
+ },
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(obj).
+ Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ }
+
+ _, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(HaveOccurred())
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"),
+ *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get Source object"),
+ }))
+ })
+
+ t.Run("handles ACL error for ChartRef", func(t *testing.T) {
+ g := NewWithT(t)
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock-other",
+ },
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(obj).
+ Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ }
+
+ res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(HaveOccurred())
+ g.Expect(errors.Is(err, reconcile.TerminalError(nil))).To(BeTrue())
+ g.Expect(res.IsZero()).To(BeTrue())
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.StalledCondition, acl.AccessDeniedReason, "cross-namespace references are not allowed"),
+ *conditions.FalseCondition(meta.ReadyCondition, acl.AccessDeniedReason, "cross-namespace references are not allowed"),
+ }))
+ })
+
+ t.Run("waits for ChartRef to have an Artifact", func(t *testing.T) {
+ g := NewWithT(t)
+
+ ocirepo := &sourcev1b2.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ocirepo",
+ Namespace: "mock",
+ Generation: 2,
+ },
+ Status: sourcev1b2.OCIRepositoryStatus{
+ ObservedGeneration: 2,
+ Artifact: nil,
+ Conditions: []metav1.Condition{
+ {
+ Type: meta.ReadyCondition,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ },
+ }
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock",
+ },
+ Interval: metav1.Duration{Duration: 1 * time.Second},
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(ocirepo, obj).
+ Build(),
+ }
+
+ res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(Equal(errWaitForChart))
+ g.Expect(res.RequeueAfter).To(Equal(obj.Spec.Interval.Duration))
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
+ *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "OCIRepository 'mock/ocirepo' is not ready"),
+ }))
+ })
+
+ t.Run("confirms ChartRef has an Artifact", func(t *testing.T) {
+ g := NewWithT(t)
+
+ ocirepo := &sourcev1b2.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ocirepo",
+ Namespace: "mock",
+ Generation: 2,
+ },
+ Status: sourcev1b2.OCIRepositoryStatus{
+ ObservedGeneration: 2,
+ Artifact: nil,
+ Conditions: []metav1.Condition{
+ {
+ Type: meta.ReadyCondition,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ },
+ }
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock",
+ },
+ Interval: metav1.Duration{Duration: 1 * time.Second},
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(ocirepo, obj).
+ Build(),
+ }
+
+ res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(Equal(errWaitForChart))
+ g.Expect(res.RequeueAfter).To(Equal(obj.Spec.Interval.Duration))
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
+ *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "OCIRepository 'mock/ocirepo' is not ready"),
+ }))
+ })
+
+ t.Run("reports values composition failure", func(t *testing.T) {
+ g := NewWithT(t)
+
+ ocirepo := &sourcev1b2.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ocirepo",
+ Namespace: "mock",
+ Generation: 2,
+ },
+ Spec: sourcev1b2.OCIRepositorySpec{
+ Interval: metav1.Duration{Duration: 1 * time.Second},
+ },
+ Status: sourcev1b2.OCIRepositoryStatus{
+ ObservedGeneration: 2,
+ Artifact: &sourcev1.Artifact{},
+ Conditions: []metav1.Condition{
+ {
+ Type: meta.ReadyCondition,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ },
+ }
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock",
+ },
+ ValuesFrom: []v2.ValuesReference{
+ {
+ Kind: "Secret",
+ Name: "missing",
+ },
+ },
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(ocirepo, obj).
+ Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ }
+
+ _, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(HaveOccurred())
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"),
+ *conditions.FalseCondition(meta.ReadyCondition, "ValuesError", "could not resolve Secret chart values reference 'mock/missing' with key 'values.yaml'"),
+ }))
+ })
+
+ t.Run("reports Helm chart load failure", func(t *testing.T) {
+ g := NewWithT(t)
+
+ ocirepo := &sourcev1b2.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ocirepo",
+ Namespace: "mock",
+ Generation: 2,
+ },
+ Spec: sourcev1b2.OCIRepositorySpec{
+ Interval: metav1.Duration{Duration: 1 * time.Second},
+ },
+ Status: sourcev1b2.OCIRepositoryStatus{
+ ObservedGeneration: 2,
+ Artifact: &sourcev1.Artifact{
+ URL: testServer.URL() + "/does-not-exist",
+ },
+ Conditions: []metav1.Condition{
+ {
+ Type: meta.ReadyCondition,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ },
+ }
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock",
+ },
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(ocirepo, obj).
+ Build(),
+ requeueDependency: 10 * time.Second,
+ EventRecorder: record.NewFakeRecorder(32),
+ }
+
+ res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(Equal(errWaitForDependency))
+ g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
+ *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"),
+ }))
+ })
+ t.Run("report helmChart load failure when switching from existing HelmChat to chartRef", func(t *testing.T) {
+ g := NewWithT(t)
+
+ chart := &sourcev1b2.HelmChart{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "chart",
+ Namespace: "mock",
+ Generation: 1,
+ },
+ Status: sourcev1b2.HelmChartStatus{
+ ObservedGeneration: 1,
+ Artifact: &sourcev1.Artifact{},
+ Conditions: []metav1.Condition{
+ {
+ Type: meta.ReadyCondition,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ },
+ }
+
+ ocirepo := &sourcev1b2.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ocirepo",
+ Namespace: "mock",
+ Generation: 2,
+ },
+ Spec: sourcev1b2.OCIRepositorySpec{
+ Interval: metav1.Duration{Duration: 1 * time.Second},
+ },
+ Status: sourcev1b2.OCIRepositoryStatus{
+ ObservedGeneration: 2,
+ Artifact: &sourcev1.Artifact{
+ URL: testServer.URL() + "/does-not-exist",
+ },
+ Conditions: []metav1.Condition{
+ {
+ Type: meta.ReadyCondition,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ },
+ }
+
+ obj := &v2.HelmRelease{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "release",
+ Namespace: "mock",
+ },
+ Spec: v2.HelmReleaseSpec{
+ ChartRef: &v2.CrossNamespaceSourceReference{
+ Kind: "OCIRepository",
+ Name: "ocirepo",
+ Namespace: "mock",
+ },
+ },
+ Status: v2.HelmReleaseStatus{
+ HelmChart: "mock/chart",
+ },
+ }
+
+ r := &HelmReleaseReconciler{
+ Client: fake.NewClientBuilder().
+ WithScheme(NewTestScheme()).
+ WithStatusSubresource(&v2.HelmRelease{}).
+ WithObjects(chart, ocirepo, obj).
+ Build(),
+ requeueDependency: 10 * time.Second,
+ EventRecorder: record.NewFakeRecorder(32),
+ }
+
+ res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
+ g.Expect(err).To(Equal(errWaitForDependency))
+ g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
+ *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"),
+ }))
+ })
+}
+
func TestHelmReleaseReconciler_reconcileDelete(t *testing.T) {
t.Run("uninstalls Helm release and removes chart", func(t *testing.T) {
g := NewWithT(t)
@@ -1020,7 +1404,7 @@ func TestHelmReleaseReconciler_reconcileDelete(t *testing.T) {
})
}
-func TestHelmReleaseReconciler_reconileReleaseDeletion(t *testing.T) {
+func TestHelmReleaseReconciler_reconcileReleaseDeletion(t *testing.T) {
t.Run("uninstalls Helm release", func(t *testing.T) {
g := NewWithT(t)