Skip to content

Commit cf3735e

Browse files
committed
Static helmrepository OCI
Remove the HelmRepositoryOCI reconciler and make HelmRepository of type OCI static. The existing HelmRepository OCI objects are migrated to static object by removing their finalizers and status. New HelmRepository OCI objects go through one time migration to remove the status. These are not reconciled again, unless the type is changed to default. On type switching from HelmRepository default to OCI, the finalizer, status and artifact are removed to make the object static. On switching from OCI to default, a complete reconciliation of HelmRepository takes place to build artifact and add status and finalizer. The HelmRepository .spec.url has a new validation to check the URL scheme. This is to add some validation to HelmRepository OCI since it's not backed by a reconciler for full validation. Add HelmRepositoryOCIMigrationPredicate predicate to detect and allow reconciliation of HelmRepository OCI objects that need migration. The other predicates that filtered the HelmRepository events based on the type have been removed as all the HelmRepositories will now be reconciled by a single reconciler. HelmRepositoryOCIMigrationPredicate readily allows non-OCI objects and only checks if a migration is needed for OCI type object. Add controller tests for different migration scenarios. Signed-off-by: Sunny <[email protected]>
1 parent f54a59c commit cf3735e

13 files changed

+395
-1070
lines changed

api/v1beta2/helmrepository_types.go

+1
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

+1
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

-2
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

+7
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"
@@ -139,6 +140,12 @@ type HelmChartReconciler struct {
139140
patchOptions []patch.Option
140141
}
141142

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

internal/controller/helmchart_controller_test.go

+1-1
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

+52-6
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"
@@ -138,10 +140,7 @@ func (r *HelmRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager,
138140
For(&helmv1.HelmRepository{}).
139141
WithEventFilter(
140142
predicate.And(
141-
predicate.Or(
142-
intpredicates.HelmRepositoryTypePredicate{RepositoryType: helmv1.HelmRepositoryTypeDefault},
143-
intpredicates.HelmRepositoryTypePredicate{RepositoryType: ""},
144-
),
143+
intpredicates.HelmRepositoryOCIMigrationPredicate{},
145144
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
146145
),
147146
).
@@ -164,6 +163,11 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
164163
// Initialize the patch helper with the current version of the object.
165164
serialPatcher := patch.NewSerialPatcher(obj, r.Client)
166165

166+
// If it's of type OCI, migrate the object to static.
167+
if obj.Spec.Type == helmv1.HelmRepositoryTypeOCI {
168+
return r.migrationToStatic(ctx, serialPatcher, obj)
169+
}
170+
167171
// recResult stores the abstracted reconcile result.
168172
var recResult sreconcile.Result
169173

@@ -193,8 +197,8 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
193197
r.Metrics.RecordDuration(ctx, obj, start)
194198
}()
195199

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) {
200+
// Examine if the object is under deletion.
201+
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
198202
recResult, retErr = r.reconcileDelete(ctx, obj)
199203
return
200204
}
@@ -391,6 +395,18 @@ func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, sp *pat
391395
// pointer is set to the newly fetched index.
392396
func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch.SerialPatcher,
393397
obj *helmv1.HelmRepository, artifact *sourcev1.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) {
398+
// Ensure it's not an OCI URL. API validation ensures that only
399+
// http/https/oci scheme are allowed.
400+
if strings.HasPrefix(obj.Spec.URL, helmreg.OCIScheme) {
401+
err := fmt.Errorf("'oci' URL scheme cannot be used with 'default' HelmRepository type")
402+
e := serror.NewStalling(
403+
fmt.Errorf("invalid Helm repository URL: %w", err),
404+
sourcev1.URLInvalidReason,
405+
)
406+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
407+
return sreconcile.ResultEmpty, e
408+
}
409+
394410
normalizedURL, err := repository.NormalizeURL(obj.Spec.URL)
395411
if err != nil {
396412
e := serror.NewStalling(
@@ -685,3 +701,33 @@ func (r *HelmRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Ob
685701
}
686702
r.Eventf(obj, eventType, reason, msg)
687703
}
704+
705+
// migrateToStatic is HelmRepository OCI migration to static object.
706+
func (r *HelmRepositoryReconciler) migrationToStatic(ctx context.Context, sp *patch.SerialPatcher, obj *helmv1.HelmRepository) (result ctrl.Result, err error) {
707+
// Skip migration if suspended and not being deleted.
708+
if obj.Spec.Suspend && obj.DeletionTimestamp.IsZero() {
709+
return ctrl.Result{}, nil
710+
}
711+
712+
if !intpredicates.HelmRepositoryOCIRequireMigration(obj) {
713+
// Already migrated, nothing to do.
714+
return ctrl.Result{}, nil
715+
}
716+
717+
// Delete any artifact.
718+
_, err = r.reconcileDelete(ctx, obj)
719+
if err != nil {
720+
return ctrl.Result{}, err
721+
}
722+
// Delete finalizer and reset the status.
723+
controllerutil.RemoveFinalizer(obj, sourcev1.SourceFinalizer)
724+
obj.Status = helmv1.HelmRepositoryStatus{}
725+
726+
if err := sp.Patch(ctx, obj); err != nil {
727+
return ctrl.Result{}, err
728+
}
729+
730+
r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "Migration",
731+
"removed artifact and finalizer to migrate to static HelmRepository of type OCI")
732+
return ctrl.Result{}, nil
733+
}

0 commit comments

Comments
 (0)