diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go index 377dc211..df717f29 100644 --- a/controllers/ocirepository_controller.go +++ b/controllers/ocirepository_controller.go @@ -102,6 +102,14 @@ var ociRepositoryFailConditions = []string{ sourcev1.StorageOperationFailedCondition, } +type invalidOCIURLError struct { + err error +} + +func (e invalidOCIURLError) Error() string { + return e.err.Error() +} + // ociRepositoryReconcileFunc is the function type for all the v1beta2.OCIRepository // (sub)reconcile functions. The type implementations are grouped and // executed serially to perform the complete reconcile of the object. @@ -337,9 +345,17 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour // Determine which artifact revision to pull url, err := r.getArtifactURL(obj, options) if err != nil { + if _, ok := err.(invalidOCIURLError); ok { + e := serror.NewStalling( + fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err), + sourcev1.URLInvalidReason) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error()) + return sreconcile.ResultEmpty, e + } + e := serror.NewGeneric( - fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err), - sourcev1.URLInvalidReason) + fmt.Errorf("failed to determine the artifact tag for '%s': %w", obj.Spec.URL, err), + sourcev1.OCIOperationFailedReason) conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error()) return sreconcile.ResultEmpty, e } @@ -464,7 +480,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository func (r *OCIRepositoryReconciler) getArtifactURL(obj *sourcev1.OCIRepository, options []crane.Option) (string, error) { url, err := r.parseRepositoryURL(obj) if err != nil { - return "", err + return "", invalidOCIURLError{err} } if obj.Spec.Reference != nil { diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go index b312fe8b..9e54e326 100644 --- a/controllers/ocirepository_controller_test.go +++ b/controllers/ocirepository_controller_test.go @@ -772,7 +772,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) { want: sreconcile.ResultEmpty, wantErr: true, assertConditions: []metav1.Condition{ - *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.URLInvalidReason, "no match found for semver:"), + *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to determine the artifact tag for 'oci://%s/podinfo': no match found for semver: <= 6.1.0", server.registryHost), }, }, { @@ -1064,6 +1064,48 @@ func TestOCIRepository_getArtifactURL(t *testing.T) { } } +func TestOCIRepository_stalled(t *testing.T) { + g := NewWithT(t) + + ns, err := testEnv.CreateNamespace(ctx, "ocirepository-stalled-test") + g.Expect(err).ToNot(HaveOccurred()) + defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }() + + obj := &sourcev1.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "ocirepository-reconcile", + Namespace: ns.Name, + }, + Spec: sourcev1.OCIRepositorySpec{ + URL: "oci://ghcr.io/test/test:v1", + Interval: metav1.Duration{Duration: 60 * time.Minute}, + }, + } + + g.Expect(testEnv.Create(ctx, obj)).To(Succeed()) + + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + resultobj := sourcev1.OCIRepository{} + + // Wait for the object to fail + g.Eventually(func() bool { + if err := testEnv.Get(ctx, key, &resultobj); err != nil { + return false + } + readyCondition := conditions.Get(&resultobj, meta.ReadyCondition) + if readyCondition == nil { + return false + } + return obj.Generation == readyCondition.ObservedGeneration && + !conditions.IsReady(&resultobj) + }, timeout).Should(BeTrue()) + + // Verify that stalled condition is present in status + stalledCondition := conditions.Get(&resultobj, meta.StalledCondition) + g.Expect(stalledCondition).ToNot(BeNil()) + g.Expect(stalledCondition.Reason).Should(Equal(sourcev1.URLInvalidReason)) +} + func TestOCIRepository_reconcileStorage(t *testing.T) { g := NewWithT(t)