Skip to content

Commit cb28864

Browse files
committed
Static helmrepository OCI
Signed-off-by: Sunny <[email protected]>
1 parent 53ee3a3 commit cb28864

13 files changed

+453
-963
lines changed

api/v1beta2/helmrepository_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
type HelmRepositorySpec struct {
4545
// URL of the Helm repository, a valid URL contains at least a protocol and
4646
// host.
47+
// +kubebuilder:validation:Pattern="^(http|https|oci)://.*$"
4748
// +required
4849
URL string `json:"url"`
4950

config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ spec:
373373
url:
374374
description: URL of the Helm repository, a valid URL contains at least
375375
a protocol and host.
376+
pattern: ^(http|https|oci)://.*$
376377
type: string
377378
required:
378379
- interval

hack/ci/e2e.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ kubectl -n source-system rollout status deploy/source-controller --timeout=1m
7575
kubectl -n source-system wait gitrepository/gitrepository-sample --for=condition=ready --timeout=1m
7676
kubectl -n source-system wait ocirepository/ocirepository-sample --for=condition=ready --timeout=1m
7777
kubectl -n source-system wait helmrepository/helmrepository-sample --for=condition=ready --timeout=1m
78-
kubectl -n source-system wait helmrepository/helmrepository-sample-oci --for=condition=ready --timeout=1m
7978
kubectl -n source-system wait helmchart/helmchart-sample --for=condition=ready --timeout=1m
8079
kubectl -n source-system wait helmchart/helmchart-sample-oci --for=condition=ready --timeout=1m
8180
kubectl -n source-system delete -f "${ROOT_DIR}/config/samples"
@@ -145,7 +144,6 @@ kubectl -n source-system wait gitrepository/large-repo --for=condition=ready --t
145144

146145
echo "Run HelmChart from OCI registry tests"
147146
kubectl -n source-system apply -f "${ROOT_DIR}/config/testdata/helmchart-from-oci/source.yaml"
148-
kubectl -n source-system wait helmrepository/podinfo --for=condition=ready --timeout=1m
149147
kubectl -n source-system wait helmchart/podinfo --for=condition=ready --timeout=1m
150148
kubectl -n source-system wait helmchart/podinfo-keyless --for=condition=ready --timeout=1m
151149

internal/controller/helmchart_controller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"errors"
2223
"fmt"
2324
"net/url"
@@ -138,6 +139,12 @@ type HelmChartReconciler struct {
138139
patchOptions []patch.Option
139140
}
140141

142+
// RegistryClientGeneratorFunc is a function that returns a registry client
143+
// and an optional file name.
144+
// The file is used to store the registry client credentials.
145+
// The caller is responsible for deleting the file.
146+
type RegistryClientGeneratorFunc func(tlsConfig *tls.Config, isLogin bool) (*helmreg.Client, string, error)
147+
141148
func (r *HelmChartReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
142149
return r.SetupWithManagerAndOptions(ctx, mgr, HelmChartReconcilerOptions{})
143150
}

internal/controller/helmchart_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
197197
{
198198
name: "Stalling on invalid repository URL",
199199
beforeFunc: func(repository *helmv1.HelmRepository) {
200-
repository.Spec.URL = "://unsupported" // Invalid URL
200+
repository.Spec.URL = "https://unsupported/foo://" // Invalid URL
201201
},
202202
assertFunc: func(g *WithT, obj *helmv1.HelmChart, _ *helmv1.HelmRepository) {
203203
key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}

internal/controller/helmrepository_controller.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ import (
2222
"errors"
2323
"fmt"
2424
"net/url"
25+
"strings"
2526
"time"
2627

2728
"github.com/docker/go-units"
2829
"github.com/opencontainers/go-digest"
2930
helmgetter "helm.sh/helm/v3/pkg/getter"
31+
helmreg "helm.sh/helm/v3/pkg/registry"
3032
corev1 "k8s.io/api/core/v1"
3133
"k8s.io/apimachinery/pkg/runtime"
3234
kuberecorder "k8s.io/client-go/tools/record"
@@ -139,8 +141,10 @@ func (r *HelmRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager,
139141
WithEventFilter(
140142
predicate.And(
141143
predicate.Or(
144+
// Allow default types or OCI types that require migration.
142145
intpredicates.HelmRepositoryTypePredicate{RepositoryType: helmv1.HelmRepositoryTypeDefault},
143146
intpredicates.HelmRepositoryTypePredicate{RepositoryType: ""},
147+
intpredicates.HelmRepositoryOCIMigrationPredicate{},
144148
),
145149
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
146150
),
@@ -164,6 +168,36 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
164168
// Initialize the patch helper with the current version of the object.
165169
serialPatcher := patch.NewSerialPatcher(obj, r.Client)
166170

171+
// HelmRepository OCI migration.
172+
//
173+
// If it's of type OCI, migrate the object.
174+
if obj.Spec.Type == helmv1.HelmRepositoryTypeOCI {
175+
// Skip migration if suspended and not being deleted.
176+
if obj.Spec.Suspend && obj.DeletionTimestamp.IsZero() {
177+
return ctrl.Result{}, nil
178+
}
179+
180+
if !intpredicates.HelmRepositoryOCIRequireMigration(obj) {
181+
// Already migrated, nothing to do.
182+
return ctrl.Result{}, nil
183+
}
184+
185+
// Delete any artifact.
186+
_, err := r.reconcileDelete(ctx, obj)
187+
if err != nil {
188+
return ctrl.Result{}, err
189+
}
190+
// Delete finalizer and reset the status.
191+
controllerutil.RemoveFinalizer(obj, sourcev1.SourceFinalizer)
192+
obj.Status = helmv1.HelmRepositoryStatus{}
193+
194+
if err := serialPatcher.Patch(ctx, obj); err != nil {
195+
return ctrl.Result{}, err
196+
}
197+
198+
return ctrl.Result{}, nil
199+
}
200+
167201
// recResult stores the abstracted reconcile result.
168202
var recResult sreconcile.Result
169203

@@ -193,8 +227,8 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
193227
r.Metrics.RecordDuration(ctx, obj, start)
194228
}()
195229

196-
// Examine if the object is under deletion or if a type change has happened.
197-
if !obj.ObjectMeta.DeletionTimestamp.IsZero() || (obj.Spec.Type != "" && obj.Spec.Type != helmv1.HelmRepositoryTypeDefault) {
230+
// Examine if the object is under deletion.
231+
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
198232
recResult, retErr = r.reconcileDelete(ctx, obj)
199233
return
200234
}
@@ -391,6 +425,18 @@ func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, sp *pat
391425
// pointer is set to the newly fetched index.
392426
func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher,
393427
obj *helmv1.HelmRepository, artifact *sourcev1.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) {
428+
// Ensure it's not an OCI URL. API validation ensures that only
429+
// http/https/oci scheme are allowed.
430+
if strings.HasPrefix(obj.Spec.URL, helmreg.OCIScheme) {
431+
err := fmt.Errorf("'oci' URL scheme cannot be used with 'default' HelmRepository type")
432+
e := serror.NewStalling(
433+
fmt.Errorf("invalid Helm repository URL: %w", err),
434+
sourcev1.URLInvalidReason,
435+
)
436+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, err.Error())
437+
return sreconcile.ResultEmpty, e
438+
}
439+
394440
normalizedURL, err := repository.NormalizeURL(obj.Spec.URL)
395441
if err != nil {
396442
e := serror.NewStalling(

0 commit comments

Comments
 (0)