Skip to content

Commit 0986524

Browse files
committed
Add ExternalArtifact feature gate
Signed-off-by: Stefan Prodan <[email protected]>
1 parent 3f63163 commit 0986524

File tree

11 files changed

+132
-77
lines changed

11 files changed

+132
-77
lines changed

config/default/kustomization.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
namespace: helm-system
44
resources:
5-
- https://github.com/fluxcd/source-controller/releases/download/v1.7.0-rc.2/source-controller.crds.yaml
6-
- https://github.com/fluxcd/source-controller/releases/download/v1.7.0-rc.2/source-controller.deployment.yaml
5+
- https://github.com/fluxcd/source-controller/releases/download/v1.7.0-rc.3/source-controller.crds.yaml
6+
- https://github.com/fluxcd/source-controller/releases/download/v1.7.0-rc.3/source-controller.deployment.yaml
77
- ../crd
88
- ../rbac
99
- ../manager

docs/spec/v2/helmreleases.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,16 @@ HelmRelease object.
205205

206206
### Chart reference
207207

208-
`.spec.chartRef` is an optional field used to refer to an [OCIRepository resource](https://fluxcd.io/flux/components/source/ocirepositories/) or a [HelmChart resource](https://fluxcd.io/flux/components/source/helmcharts/)
209-
from which to fetch the Helm chart. The chart is fetched by the controller with the
210-
information provided by `.status.artifact` of the referenced resource.
208+
`.spec.chartRef` is an optional field used to refer to the Source object which has an
209+
Artifact containing the Helm chart. It has two required fields:
211210

212-
For a referenced resource of `kind OCIRepository`, the chart version of the last
211+
- `kind`: The Kind of the referred Source object. Supported Source types:
212+
+ [OCIRepository](https://fluxcd.io/flux/components/source/ocirepositories/)
213+
+ [HelmChart](https://fluxcd.io/flux/components/source/helmcharts/)
214+
+ [ExternalArtifact](https://fluxcd.io/flux/components/source/externalartifacts/) (requires `--feature-gates=ExternalArtifact=true` flag)
215+
- `name`: The Name of the referred Source object.
216+
217+
For a referenced resource of kind `OCIRepository`, the chart version of the last
213218
release attempt is reported in `.status.lastAttemptedRevision`. The version is in
214219
the format `<version>+<digest[0:12]>`. The digest of the OCI artifact is appended
215220
to the version to ensure that a change in the artifact content triggers a new release.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require (
2828
github.com/fluxcd/pkg/runtime v0.83.0
2929
github.com/fluxcd/pkg/ssa v0.53.0
3030
github.com/fluxcd/pkg/testserver v0.13.0
31-
github.com/fluxcd/source-controller/api v1.7.0-rc.2
31+
github.com/fluxcd/source-controller/api v1.7.0-rc.3
3232
github.com/go-logr/logr v1.4.3
3333
github.com/google/cel-go v0.26.1
3434
github.com/google/go-cmp v0.7.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ github.com/fluxcd/pkg/ssa v0.53.0 h1:EtKFAYWXohIGkzPhIrv8NbV5zYr4iZHY6DaNxMR9+bU
166166
github.com/fluxcd/pkg/ssa v0.53.0/go.mod h1:0IZxnnH8XkDkFzWjoMJsFEwPIWPOk3Gc/WR5+gT/KgI=
167167
github.com/fluxcd/pkg/testserver v0.13.0 h1:xEpBcEYtD7bwvZ+i0ZmChxKkDo/wfQEV3xmnzVybSSg=
168168
github.com/fluxcd/pkg/testserver v0.13.0/go.mod h1:akRYv3FLQUsme15na9ihECRG6hBuqni4XEY9W8kzs8E=
169-
github.com/fluxcd/source-controller/api v1.7.0-rc.2 h1:ny21QMsZ1Gs5t5Rx7Pd1s0xc5UT7B4hGySzX+mhWHnw=
170-
github.com/fluxcd/source-controller/api v1.7.0-rc.2/go.mod h1:sbJibK4Ik+2AuTRRLXPA+n2u6nLUIGaxC07ava+RqeM=
169+
github.com/fluxcd/source-controller/api v1.7.0-rc.3 h1:+9cd//77LAgp0XRe97CXUaPnu78jsRNWTXq95GHGhgc=
170+
github.com/fluxcd/source-controller/api v1.7.0-rc.3/go.mod h1:sbJibK4Ik+2AuTRRLXPA+n2u6nLUIGaxC07ava+RqeM=
171171
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
172172
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
173173
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=

internal/controller/helmrelease_controller.go

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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
101+
102+
DependencyRequeueInterval time.Duration
103+
ArtifactFetchRetries int
100104

101-
requeueDependency time.Duration
102-
artifactFetchRetries int
105+
// Feature gates
106+
107+
AdditiveCELDependencyCheck bool
108+
AllowExternalArtifact bool
109+
DisableChartDigestTracking bool
103110
}
104111

105112
var (
@@ -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)
@@ -827,6 +834,13 @@ func (r *HelmReleaseReconciler) getSourceFromExternalArtifact(ctx context.Contex
827834
return nil, err
828835
}
829836

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+
830844
or := sourcev1.ExternalArtifact{}
831845
if err := r.Client.Get(ctx, sourceRef, &or); err != nil {
832846
return nil, err

internal/controller/helmrelease_controller_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
107107
WithStatusSubresource(&v2.HelmRelease{}).
108108
WithObjects(dependency, obj).
109109
Build(),
110-
EventRecorder: record.NewFakeRecorder(32),
111-
requeueDependency: 5 * time.Second,
110+
EventRecorder: record.NewFakeRecorder(32),
111+
DependencyRequeueInterval: 5 * time.Second,
112112
}
113113
r.APIReader = r.Client
114114

115115
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
116116
g.Expect(err).To(Equal(errWaitForDependency))
117-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
117+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
118118

119119
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
120120
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -235,7 +235,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
235235

236236
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
237237
g.Expect(err).To(Equal(errWaitForChart))
238-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
238+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
239239

240240
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
241241
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -292,7 +292,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
292292

293293
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
294294
g.Expect(err).To(Equal(errWaitForChart))
295-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
295+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
296296

297297
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
298298
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -400,13 +400,13 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
400400
WithStatusSubresource(&v2.HelmRelease{}).
401401
WithObjects(chart, obj).
402402
Build(),
403-
requeueDependency: 10 * time.Second,
403+
DependencyRequeueInterval: 10 * time.Second,
404404
}
405405
r.APIReader = r.Client
406406

407407
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
408408
g.Expect(err).To(Equal(errWaitForDependency))
409-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
409+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
410410

411411
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
412412
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -1112,7 +1112,7 @@ func TestHelmReleaseReconciler_reconcileReleaseFromHelmChartSource(t *testing.T)
11121112

11131113
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
11141114
g.Expect(err).To(Equal(errWaitForChart))
1115-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
1115+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
11161116

11171117
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
11181118
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -1166,13 +1166,13 @@ func TestHelmReleaseReconciler_reconcileReleaseFromHelmChartSource(t *testing.T)
11661166
WithStatusSubresource(&v2.HelmRelease{}).
11671167
WithObjects(chart, obj).
11681168
Build(),
1169-
requeueDependency: 10 * time.Second,
1170-
EventRecorder: record.NewFakeRecorder(32),
1169+
DependencyRequeueInterval: 10 * time.Second,
1170+
EventRecorder: record.NewFakeRecorder(32),
11711171
}
11721172

11731173
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
11741174
g.Expect(err).To(Equal(errWaitForDependency))
1175-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
1175+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
11761176

11771177
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
11781178
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -1246,13 +1246,13 @@ func TestHelmReleaseReconciler_reconcileReleaseFromHelmChartSource(t *testing.T)
12461246
WithStatusSubresource(&v2.HelmRelease{}).
12471247
WithObjects(chart, sharedChart, obj).
12481248
Build(),
1249-
requeueDependency: 10 * time.Second,
1250-
EventRecorder: record.NewFakeRecorder(32),
1249+
DependencyRequeueInterval: 10 * time.Second,
1250+
EventRecorder: record.NewFakeRecorder(32),
12511251
}
12521252

12531253
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
12541254
g.Expect(err).To(Equal(errWaitForDependency))
1255-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
1255+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
12561256

12571257
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
12581258
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -1579,7 +1579,7 @@ func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testin
15791579

15801580
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
15811581
g.Expect(err).To(Equal(errWaitForChart))
1582-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
1582+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
15831583

15841584
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
15851585
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -1695,13 +1695,13 @@ func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testin
16951695
WithStatusSubresource(&v2.HelmRelease{}).
16961696
WithObjects(ocirepo, obj).
16971697
Build(),
1698-
requeueDependency: 10 * time.Second,
1699-
EventRecorder: record.NewFakeRecorder(32),
1698+
DependencyRequeueInterval: 10 * time.Second,
1699+
EventRecorder: record.NewFakeRecorder(32),
17001700
}
17011701

17021702
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
17031703
g.Expect(err).To(Equal(errWaitForDependency))
1704-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
1704+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
17051705

17061706
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
17071707
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),
@@ -1775,13 +1775,13 @@ func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testin
17751775
WithStatusSubresource(&v2.HelmRelease{}).
17761776
WithObjects(chart, ocirepo, obj).
17771777
Build(),
1778-
requeueDependency: 10 * time.Second,
1779-
EventRecorder: record.NewFakeRecorder(32),
1778+
DependencyRequeueInterval: 10 * time.Second,
1779+
EventRecorder: record.NewFakeRecorder(32),
17801780
}
17811781

17821782
res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj)
17831783
g.Expect(err).To(Equal(errWaitForDependency))
1784-
g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency))
1784+
g.Expect(res.RequeueAfter).To(Equal(r.DependencyRequeueInterval))
17851785

17861786
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
17871787
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""),

internal/controller/helmrelease_manager.go

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package controller
1919
import (
2020
"context"
2121
"fmt"
22-
"time"
2322

2423
"github.com/fluxcd/pkg/runtime/predicates"
2524
sourcev1 "github.com/fluxcd/source-controller/api/v1"
@@ -38,10 +37,9 @@ import (
3837
)
3938

4039
type HelmReleaseReconcilerOptions struct {
41-
HTTPRetry int
42-
DependencyRequeueInterval time.Duration
43-
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
44-
WatchConfigsPredicate predicate.Predicate
40+
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
41+
WatchExternalArtifacts bool
42+
WatchConfigsPredicate predicate.Predicate
4543
}
4644

4745
func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts HelmReleaseReconcilerOptions) error {
@@ -116,10 +114,7 @@ func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.M
116114
return err
117115
}
118116

119-
r.requeueDependency = opts.DependencyRequeueInterval
120-
r.artifactFetchRetries = opts.HTTPRetry
121-
122-
return ctrl.NewControllerManagedBy(mgr).
117+
ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
123118
For(&v2.HelmRelease{}, builder.WithPredicates(
124119
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
125120
)).
@@ -133,11 +128,6 @@ func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.M
133128
handler.EnqueueRequestsFromMapFunc(r.requestsForOCIRepositoryChange),
134129
builder.WithPredicates(intpredicates.SourceRevisionChangePredicate{}),
135130
).
136-
Watches(
137-
&sourcev1.ExternalArtifact{},
138-
handler.EnqueueRequestsFromMapFunc(r.requestsForExternalArtifactChange),
139-
builder.WithPredicates(intpredicates.SourceRevisionChangePredicate{}),
140-
).
141131
WatchesMetadata(
142132
&corev1.ConfigMap{},
143133
handler.EnqueueRequestsFromMapFunc(r.requestsForConfigDependency(indexConfigMap)),
@@ -147,9 +137,16 @@ func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.M
147137
&corev1.Secret{},
148138
handler.EnqueueRequestsFromMapFunc(r.requestsForConfigDependency(indexSecret)),
149139
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, opts.WatchConfigsPredicate),
150-
).
151-
WithOptions(controller.Options{
152-
RateLimiter: opts.RateLimiter,
153-
}).
154-
Complete(r)
140+
)
141+
142+
if opts.WatchExternalArtifacts {
143+
ctrlBuilder = ctrlBuilder.Watches(
144+
&sourcev1.ExternalArtifact{},
145+
handler.EnqueueRequestsFromMapFunc(r.requestsForExternalArtifactChange),
146+
builder.WithPredicates(intpredicates.SourceRevisionChangePredicate{}),
147+
)
148+
}
149+
150+
return ctrlBuilder.WithOptions(controller.Options{RateLimiter: opts.RateLimiter}).Complete(r)
151+
155152
}

0 commit comments

Comments
 (0)