Skip to content

Commit 8e4aa2e

Browse files
thardeck0xavi0
andauthored
[v0.13] Align Helm deployer client construction (#5047)
* Use consistent getter in Helm configuration createCfg used the base getter for both RESTClientGetter and KubeClient independently of the getter produced in getCfg. Accept a getter parameter in createCfg so callers control which getter is used for all client construction. * Use Helm configuration getter for valuesFrom client Construct the kubernetes.Interface used to resolve valuesFrom references from the Helm configuration's RESTClientGetter. * Avoids returning an error when deleting OCI artifacts (#4221) * Avoids returning an error when deleting OCI artifacts Returning an error when deleting an OCI artifact could lead to infinite requeing if there is a permanent error accessing the registry. The registry is an external resource out of Fleet and we don't have full control. We already log an event when there is an error deleting the OCI artifact. This was leading to errors in e2e tests, because one of the tests was checking if there was an error when trying to use credentials with no delete permissions for the OCI registry. If the test was the last one, it was logging the expected event and CI was successful, otherwise the next test was failing randomly depending on which test was (if the test was reseting the OCI storage env variable then the controller was restarted and unblocked the situation). Orders the tests and now the only test that needs the OCI_STORAGE=false env variable is executed the last one. --------- Signed-off-by: Xavi Garcia <xavi.garcia@suse.com> --------- Signed-off-by: Xavi Garcia <xavi.garcia@suse.com> Co-authored-by: Xavi Garcia <xavi.garcia@suse.com>
1 parent 3694615 commit 8e4aa2e

5 files changed

Lines changed: 223 additions & 73 deletions

File tree

e2e/single-cluster/oci_registry_test.go

Lines changed: 58 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func getOCIRegistryExternalIP(k kubectl.Command) string {
232232
return externalIP
233233
}
234234

235-
var _ = Describe("Single Cluster Deployments using OCI registry", Label("oci-registry", "infra-setup"), func() {
235+
var _ = Describe("Single Cluster Deployments using OCI registry", Label("oci-registry", "infra-setup"), Ordered, func() {
236236
const asset = "single-cluster/test-oci.yaml"
237237
var (
238238
k kubectl.Command
@@ -372,10 +372,12 @@ var _ = Describe("Single Cluster Deployments using OCI registry", Label("oci-reg
372372
if contentIDToPurge != "" {
373373
k8sclient.ObjectShouldNotExist(clientUpstream, contentIDToPurge, "", &fleet.Content{}, true)
374374
} else if contentsID != "" {
375-
// check that the oci artifact was deleted
376-
_, err = ocistorage.NewOCIWrapper().PullManifest(context.TODO(), ociOpts, contentsID)
377-
Expect(err).To(HaveOccurred())
378-
Expect(err.Error()).To(ContainSubstring("not found"))
375+
Eventually(func(g Gomega) {
376+
// check that the oci artifact was deleted
377+
_, err = ocistorage.NewOCIWrapper().PullManifest(context.TODO(), ociOpts, contentsID)
378+
g.Expect(err).To(HaveOccurred())
379+
g.Expect(err.Error()).To(ContainSubstring("not found"))
380+
}).Should(Succeed())
379381
}
380382

381383
_, err = k.Delete("events", "-n", env.Namespace, "--all")
@@ -483,57 +485,6 @@ var _ = Describe("Single Cluster Deployments using OCI registry", Label("oci-reg
483485
})
484486
})
485487

486-
When("applying a gitrepo with the default ociSecret also deployed with incorrect reference and OCI env var is not set", func() {
487-
Context("containing a public oci based helm chart", func() {
488-
BeforeEach(func() {
489-
envVarValue = "false"
490-
insecureSkipTLS = true
491-
deployDefaultSecret = true
492-
deploySpecificSecretName = ""
493-
forceDefaultReference = "not-valid-oci-registry.com"
494-
forceUserDefinedReference = ""
495-
useReaderAsWriter = false
496-
useNoDeleterUser = false
497-
})
498-
499-
It("creates the bundle with no OCI storage", func() {
500-
var bundle fleet.Bundle
501-
By("creating the bundle", func() {
502-
k8sclient.GetObjectShouldSucceed(clientUpstream, "sample-simple-chart-oci", env.Namespace, &bundle)
503-
})
504-
By("not setting the ContentsID field in the bundle", func() {
505-
Expect(bundle.Spec.ContentsID).To(BeEmpty())
506-
})
507-
By("creating a bundle deployment", func() {
508-
var bd fleet.BundleDeployment
509-
k8sclient.GetObjectShouldSucceed(clientUpstream, "sample-simple-chart-oci", downstreamNamespace, &bd)
510-
Expect(bd.Spec.DeploymentID).ToNot(BeEmpty())
511-
tokens := strings.Split(bd.Spec.DeploymentID, ":")
512-
Expect(tokens).To(HaveLen(2))
513-
contentsID = tokens[0]
514-
// save the contents id to purge after the test
515-
// this will prevent interference with the previous test
516-
// as the resources sha256 is the same
517-
contentIDToPurge = contentsID
518-
})
519-
By("creating a content resource with the expected ID", func() {
520-
var content fleet.Content
521-
k8sclient.GetObjectShouldSucceed(clientUpstream, contentsID, "", &content)
522-
})
523-
By("not creating a bundle secret", func() {
524-
k8sclient.ObjectShouldNotExist(clientUpstream, contentsID, env.Namespace, &corev1.Secret{}, false)
525-
})
526-
By("not creating a bundle deployment secret", func() {
527-
k8sclient.ObjectShouldNotExist(clientUpstream, contentsID, downstreamNamespace, &corev1.Secret{}, false)
528-
})
529-
By("deploying the helm chart", func() {
530-
var cm corev1.ConfigMap
531-
k8sclient.GetObjectShouldSucceed(clientUpstream, "test-simple-chart-config", "default", &cm)
532-
})
533-
})
534-
})
535-
})
536-
537488
When("creating a gitrepo resource with invalid ociRegistry info", func() {
538489
Context("containing a public oci based helm chart", func() {
539490
BeforeEach(func() {
@@ -951,4 +902,55 @@ var _ = Describe("Single Cluster Deployments using OCI registry", Label("oci-reg
951902
})
952903
})
953904
})
905+
906+
When("applying a gitrepo with the default ociSecret also deployed with incorrect reference and OCI env var is not set", func() {
907+
Context("containing a public oci based helm chart", func() {
908+
BeforeEach(func() {
909+
envVarValue = "false"
910+
insecureSkipTLS = true
911+
deployDefaultSecret = true
912+
deploySpecificSecretName = ""
913+
forceDefaultReference = "not-valid-oci-registry.com"
914+
forceUserDefinedReference = ""
915+
useReaderAsWriter = false
916+
useNoDeleterUser = false
917+
})
918+
919+
It("creates the bundle with no OCI storage", func() {
920+
var bundle fleet.Bundle
921+
By("creating the bundle", func() {
922+
k8sclient.GetObjectShouldSucceed(clientUpstream, "sample-simple-chart-oci", env.Namespace, &bundle)
923+
})
924+
By("not setting the ContentsID field in the bundle", func() {
925+
Expect(bundle.Spec.ContentsID).To(BeEmpty())
926+
})
927+
By("creating a bundle deployment", func() {
928+
var bd fleet.BundleDeployment
929+
k8sclient.GetObjectShouldSucceed(clientUpstream, "sample-simple-chart-oci", downstreamNamespace, &bd)
930+
Expect(bd.Spec.DeploymentID).ToNot(BeEmpty())
931+
tokens := strings.Split(bd.Spec.DeploymentID, ":")
932+
Expect(tokens).To(HaveLen(2))
933+
contentsID = tokens[0]
934+
// save the contents id to purge after the test
935+
// this will prevent interference with the previous test
936+
// as the resources sha256 is the same
937+
contentIDToPurge = contentsID
938+
})
939+
By("creating a content resource with the expected ID", func() {
940+
var content fleet.Content
941+
k8sclient.GetObjectShouldSucceed(clientUpstream, contentsID, "", &content)
942+
})
943+
By("not creating a bundle secret", func() {
944+
k8sclient.ObjectShouldNotExist(clientUpstream, contentsID, env.Namespace, &corev1.Secret{}, false)
945+
})
946+
By("not creating a bundle deployment secret", func() {
947+
k8sclient.ObjectShouldNotExist(clientUpstream, contentsID, downstreamNamespace, &corev1.Secret{}, false)
948+
})
949+
By("deploying the helm chart", func() {
950+
var cm corev1.ConfigMap
951+
k8sclient.GetObjectShouldSucceed(clientUpstream, "test-simple-chart-config", "default", &cm)
952+
})
953+
})
954+
})
955+
})
954956
})

internal/cmd/controller/reconciler/bundle_controller.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,10 @@ func (r *BundleReconciler) handleDelete(ctx context.Context, logger logr.Logger,
481481
return ctrl.Result{RequeueAfter: requeueAfterBundleDeploymentCleanup}, batchDeleteBundleDeployments(ctx, r.Client, bds)
482482
}
483483

484+
if err := r.maybeDeleteOCIArtifact(ctx, bundle); err != nil {
485+
return ctrl.Result{}, err
486+
}
487+
484488
metrics.BundleCollector.Delete(req.Name, req.Namespace)
485489
controllerutil.RemoveFinalizer(bundle, finalize.BundleFinalizer)
486490
if err := r.Update(ctx, bundle); err != nil {
@@ -492,7 +496,7 @@ func (r *BundleReconciler) handleDelete(ctx context.Context, logger logr.Logger,
492496
logger.V(1).Info("Cannot delete bundle's values secret, owner garbage collection will remove it")
493497
}
494498

495-
return ctrl.Result{}, r.maybeDeleteOCIArtifact(ctx, bundle)
499+
return ctrl.Result{}, err
496500
}
497501

498502
// ensureFinalizer adds a finalizer to a recently created bundle.
@@ -799,7 +803,11 @@ func (r *BundleReconciler) maybeDeleteOCIArtifact(ctx context.Context, bundle *f
799803
r.Recorder.Event(bundle, fleetevent.Warning, "FailedToDeleteOCIArtifact", fmt.Sprintf("deleting OCI artifact %q: %v", bundle.Spec.ContentsID, err.Error()))
800804
}
801805

802-
return err
806+
// In case there's an error deleting from the OCI registry,
807+
// we return nil because otherwise the controller would retry the operation,
808+
// and since the registry is not a component of Fleet,
809+
// we don't have full control over it.
810+
return nil
803811
}
804812

805813
func batchDeleteBundleDeployments(ctx context.Context, c client.Client, list []fleet.BundleDeployment) error {

internal/helmdeployer/deployer.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (h *Helm) Setup(ctx context.Context, client client.Client, getter genericcl
8383
h.client = client
8484
h.getter = getter
8585

86-
cfg, err := h.createCfg(ctx, "")
86+
cfg, err := h.createCfg(ctx, "", h.getter)
8787
if err != nil {
8888
return err
8989
}
@@ -147,7 +147,7 @@ func (h *Helm) getCfg(ctx context.Context, namespace, serviceAccountName string)
147147
kClient := kube.New(getter)
148148
kClient.Namespace = namespace
149149

150-
cfg, err = h.createCfg(ctx, namespace)
150+
cfg, err = h.createCfg(ctx, namespace, getter)
151151
cfg.Releases.MaxHistory = MaxHelmHistory
152152
cfg.KubeClient = kClient
153153

@@ -156,12 +156,12 @@ func (h *Helm) getCfg(ctx context.Context, namespace, serviceAccountName string)
156156
return cfg, err
157157
}
158158

159-
func (h *Helm) createCfg(ctx context.Context, namespace string) (action.Configuration, error) {
159+
func (h *Helm) createCfg(ctx context.Context, namespace string, getter genericclioptions.RESTClientGetter) (action.Configuration, error) {
160160
logger := log.FromContext(ctx).WithName("helmSDK")
161161
info := func(format string, v ...interface{}) {
162162
logger.V(1).Info(fmt.Sprintf(format, v...))
163163
}
164-
kc := kube.New(h.getter)
164+
kc := kube.New(getter)
165165
kc.Log = info
166166
clientSet, err := kc.Factory.KubernetesClientSet()
167167
if err != nil {
@@ -173,7 +173,7 @@ func (h *Helm) createCfg(ctx context.Context, namespace string) (action.Configur
173173
store.MaxHistory = MaxHelmHistory
174174

175175
return action.Configuration{
176-
RESTClientGetter: h.getter,
176+
RESTClientGetter: getter,
177177
Releases: store,
178178
KubeClient: kc,
179179
Log: info,

internal/helmdeployer/install.go

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
2121

2222
corev1 "k8s.io/api/core/v1"
23-
"k8s.io/apimachinery/pkg/types"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2424
"k8s.io/apimachinery/pkg/util/yaml"
25+
"k8s.io/client-go/kubernetes"
26+
"k8s.io/client-go/rest"
2527
"sigs.k8s.io/controller-runtime/pkg/log"
2628
)
2729

@@ -70,12 +72,22 @@ func (h *Helm) install(ctx context.Context, bundleID string, manifest *manifest.
7072
logger := log.FromContext(ctx).WithName("helm-deployer").WithName("install").WithValues("commit", manifest.Commit, "dryRun", dryRun)
7173
timeout, defaultNamespace, releaseName := h.getOpts(bundleID, options)
7274

73-
values, err := h.getValues(ctx, options, defaultNamespace)
75+
cfg, err := h.getCfg(ctx, defaultNamespace, options.ServiceAccount)
7476
if err != nil {
7577
return nil, err
7678
}
7779

78-
cfg, err := h.getCfg(ctx, defaultNamespace, options.ServiceAccount)
80+
// kubeClient is nil in template mode; getValues treats a nil client as
81+
// "skip ValuesFrom lookup" and returns only the statically defined values.
82+
var kubeClient kubernetes.Interface
83+
if !h.template {
84+
kubeClient, err = kubeClientFromGetter(cfg.RESTClientGetter)
85+
if err != nil {
86+
return nil, err
87+
}
88+
}
89+
90+
values, err := h.getValues(ctx, options, defaultNamespace, kubeClient)
7991
if err != nil {
8092
return nil, err
8193
}
@@ -311,7 +323,7 @@ func handleOrphanedRelease(ctx context.Context, cfg *action.Configuration, lastR
311323
return false, nil
312324
}
313325

314-
func (h *Helm) getValues(ctx context.Context, options fleet.BundleDeploymentOptions, defaultNamespace string) (map[string]interface{}, error) {
326+
func (h *Helm) getValues(ctx context.Context, options fleet.BundleDeploymentOptions, defaultNamespace string, kubeClient kubernetes.Interface) (map[string]interface{}, error) {
315327
if options.Helm == nil {
316328
return nil, nil
317329
}
@@ -325,8 +337,8 @@ func (h *Helm) getValues(ctx context.Context, options fleet.BundleDeploymentOpti
325337
if values == nil {
326338
values = map[string]interface{}{}
327339
}
328-
// do not run this when using template
329-
if !h.template {
340+
// kubeClient is nil in template mode; skip the cluster lookups in that case.
341+
if kubeClient != nil {
330342
for _, valuesFrom := range options.Helm.ValuesFrom {
331343
var tempValues map[string]interface{}
332344
if valuesFrom.ConfigMapKeyRef != nil {
@@ -339,8 +351,7 @@ func (h *Helm) getValues(ctx context.Context, options fleet.BundleDeploymentOpti
339351
if key == "" {
340352
key = DefaultKey
341353
}
342-
configMap := &corev1.ConfigMap{}
343-
err := h.client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, configMap)
354+
configMap, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
344355
if err != nil {
345356
return nil, err
346357
}
@@ -365,8 +376,7 @@ func (h *Helm) getValues(ctx context.Context, options fleet.BundleDeploymentOpti
365376
if key == "" {
366377
key = DefaultKey
367378
}
368-
secret := &corev1.Secret{}
369-
err := h.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
379+
secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
370380
if err != nil {
371381
return nil, err
372382
}
@@ -384,6 +394,21 @@ func (h *Helm) getValues(ctx context.Context, options fleet.BundleDeploymentOpti
384394
return values, nil
385395
}
386396

397+
// restConfigGetter produces a *rest.Config. Both
398+
// genericclioptions.RESTClientGetter and action.RESTClientGetter satisfy it.
399+
type restConfigGetter interface {
400+
ToRESTConfig() (*rest.Config, error)
401+
}
402+
403+
func kubeClientFromGetter(getter restConfigGetter) (kubernetes.Interface, error) {
404+
restConfig, err := getter.ToRESTConfig()
405+
if err != nil {
406+
return nil, err
407+
}
408+
409+
return kubernetes.NewForConfig(restConfig)
410+
}
411+
387412
func valuesFromSecret(name, namespace, key string, secret *corev1.Secret) (map[string]interface{}, error) {
388413
var m map[string]interface{}
389414
if secret == nil {

0 commit comments

Comments
 (0)