@@ -87,19 +87,26 @@ type HelmReleaseReconciler struct {
8787 kuberecorder.EventRecorder
8888 helper.Metrics
8989
90- GetClusterConfig func () (* rest.Config , error )
91- ClientOpts runtimeClient.Options
92- KubeConfigOpts runtimeClient.KubeConfigOptions
93- APIReader client.Reader
94- TokenCache * cache.TokenCache
95-
96- FieldManager string
97- DefaultServiceAccount string
98- DisableChartDigestTracking bool
99- AdditiveCELDependencyCheck bool
90+ // Kubernetes configuration
91+
92+ FieldManager string
93+ DefaultServiceAccount string
94+ GetClusterConfig func () (* rest.Config , error )
95+ ClientOpts runtimeClient.Options
96+ KubeConfigOpts runtimeClient.KubeConfigOptions
97+ APIReader client.Reader
98+ TokenCache * cache.TokenCache
99+
100+ // Retry and requeue configuration
100101
101- requeueDependency time.Duration
102- artifactFetchRetries int
102+ DependencyRequeueInterval time.Duration
103+ ArtifactFetchRetries int
104+
105+ // Feature gates
106+
107+ AdditiveCELDependencyCheck bool
108+ AllowExternalArtifact bool
109+ DisableChartDigestTracking bool
103110}
104111
105112var (
@@ -221,14 +228,14 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
221228
222229 // Retry on transient errors.
223230 msg := fmt .Sprintf ("dependencies do not meet ready condition (%s): retrying in %s" ,
224- err .Error (), r .requeueDependency .String ())
231+ err .Error (), r .DependencyRequeueInterval .String ())
225232 conditions .MarkFalse (obj , meta .ReadyCondition , v2 .DependencyNotReadyReason , "%s" , err )
226233 r .Eventf (obj , corev1 .EventTypeNormal , v2 .DependencyNotReadyReason , err .Error ())
227234 log .Info (msg )
228235
229236 // Exponential backoff would cause execution to be prolonged too much,
230237 // instead we requeue on a fixed interval.
231- return ctrl.Result {RequeueAfter : r .requeueDependency }, errWaitForDependency
238+ return ctrl.Result {RequeueAfter : r .DependencyRequeueInterval }, errWaitForDependency
232239 }
233240
234241 log .Info ("all dependencies are ready" )
@@ -268,7 +275,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
268275 conditions .MarkFalse (obj , meta .ReadyCondition , "SourceNotReady" , "%s" , msg )
269276 // Do not requeue immediately, when the artifact is created
270277 // the watcher should trigger a reconciliation.
271- return jitter .JitteredRequeueInterval (ctrl.Result {RequeueAfter : r .requeueDependency }), errWaitForChart
278+ return jitter .JitteredRequeueInterval (ctrl.Result {RequeueAfter : r .DependencyRequeueInterval }), errWaitForChart
272279 }
273280 // Remove any stale corresponding Ready=False condition with Unknown.
274281 if conditions .HasAnyReason (obj , meta .ReadyCondition , "SourceNotReady" ) {
@@ -293,13 +300,13 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
293300 }
294301
295302 // Load chart from artifact.
296- loadedChart , err := loader .SecureLoadChartFromURL (loader .NewRetryableHTTPClient (ctx , r .artifactFetchRetries ), source .GetArtifact ().URL , source .GetArtifact ().Digest )
303+ loadedChart , err := loader .SecureLoadChartFromURL (loader .NewRetryableHTTPClient (ctx , r .ArtifactFetchRetries ), source .GetArtifact ().URL , source .GetArtifact ().Digest )
297304 if err != nil {
298305 if errors .Is (err , loader .ErrFileNotFound ) {
299- msg := fmt .Sprintf ("Source not ready: artifact not found. Retrying in %s" , r .requeueDependency .String ())
306+ msg := fmt .Sprintf ("Source not ready: artifact not found. Retrying in %s" , r .DependencyRequeueInterval .String ())
300307 conditions .MarkFalse (obj , meta .ReadyCondition , v2 .ArtifactFailedReason , "%s" , msg )
301308 log .Info (msg )
302- return ctrl.Result {RequeueAfter : r .requeueDependency }, errWaitForDependency
309+ return ctrl.Result {RequeueAfter : r .DependencyRequeueInterval }, errWaitForDependency
303310 }
304311
305312 conditions .MarkFalse (obj , meta .ReadyCondition , v2 .ArtifactFailedReason , "Could not load chart: %s" , err )
@@ -774,6 +781,9 @@ func (r *HelmReleaseReconciler) getSource(ctx context.Context, obj *v2.HelmRelea
774781 if obj .Spec .ChartRef .Kind == sourcev1 .OCIRepositoryKind {
775782 return r .getSourceFromOCIRef (ctx , obj )
776783 }
784+ if obj .Spec .ChartRef .Kind == sourcev1 .ExternalArtifactKind {
785+ return r .getSourceFromExternalArtifact (ctx , obj )
786+ }
777787 name , namespace = obj .Spec .ChartRef .Name , obj .Spec .ChartRef .Namespace
778788 if namespace == "" {
779789 namespace = obj .GetNamespace ()
@@ -813,6 +823,31 @@ func (r *HelmReleaseReconciler) getSourceFromOCIRef(ctx context.Context, obj *v2
813823 return & or , nil
814824}
815825
826+ func (r * HelmReleaseReconciler ) getSourceFromExternalArtifact (ctx context.Context , obj * v2.HelmRelease ) (sourcev1.Source , error ) {
827+ name , namespace := obj .Spec .ChartRef .Name , obj .Spec .ChartRef .Namespace
828+ if namespace == "" {
829+ namespace = obj .GetNamespace ()
830+ }
831+ sourceRef := types.NamespacedName {Namespace : namespace , Name : name }
832+
833+ if err := intacl .AllowsAccessTo (obj , sourcev1 .ExternalArtifactKind , sourceRef ); err != nil {
834+ return nil , err
835+ }
836+
837+ // Check if ExternalArtifact kind is allowed.
838+ if obj .Spec .ChartRef .Kind == sourcev1 .ExternalArtifactKind && ! r .AllowExternalArtifact {
839+ return nil , acl .AccessDeniedError (
840+ fmt .Sprintf ("can't access '%s/%s/%s', %s feature gate is disabled" ,
841+ obj .Spec .ChartRef .Kind , namespace , name , features .ExternalArtifact ))
842+ }
843+
844+ or := sourcev1.ExternalArtifact {}
845+ if err := r .Client .Get (ctx , sourceRef , & or ); err != nil {
846+ return nil , err
847+ }
848+ return & or , nil
849+ }
850+
816851// waitForHistoryCacheSync returns a function that can be used to wait for the
817852// cache backing the Kubernetes client to be in sync with the current state of
818853// the v2.HelmRelease.
@@ -832,13 +867,20 @@ func (r *HelmReleaseReconciler) waitForHistoryCacheSync(obj *v2.HelmRelease) wai
832867}
833868
834869func isSourceReady (obj sourcev1.Source ) (bool , string ) {
870+ if o , ok := obj .(* sourcev1.ExternalArtifact ); ok {
871+ if obj .GetArtifact () == nil {
872+ return false , fmt .Sprintf ("ExternalArtifact '%s/%s' is not ready: does not have an artifact" ,
873+ o .GetNamespace (), o .GetName ())
874+ }
875+ return true , ""
876+ }
835877 if o , ok := obj .(conditions.Getter ); ok {
836878 return isReady (o , obj .GetArtifact ())
837879 }
838880 return false , fmt .Sprintf ("unknown sourcev1 type: %T" , obj )
839881}
840882
841- func isReady (obj conditions.Getter , artifact * sourcev1 .Artifact ) (bool , string ) {
883+ func isReady (obj conditions.Getter , artifact * meta .Artifact ) (bool , string ) {
842884 observedGen , err := object .GetStatusObservedGeneration (obj )
843885 if err != nil {
844886 return false , err .Error ()
0 commit comments