diff --git a/api/v1beta1/kustomization_types.go b/api/v1beta1/kustomization_types.go index 1d59ec49..df0949f8 100644 --- a/api/v1beta1/kustomization_types.go +++ b/api/v1beta1/kustomization_types.go @@ -240,7 +240,7 @@ func (in *Kustomization) GetStatusConditions() *[]metav1.Condition { const ( // GitRepositoryIndexKey is the key used for indexing kustomizations // based on their Git sources. - GitRepositoryIndexKey string = ".metadata.git" + GitRepositoryIndexKey string = ".metadata.gitRepository" // BucketIndexKey is the key used for indexing kustomizations // based on their S3 sources. BucketIndexKey string = ".metadata.bucket" diff --git a/config/manager/deployment.yaml b/config/manager/deployment.yaml index 76a2b7ba..2dcce470 100644 --- a/config/manager/deployment.yaml +++ b/config/manager/deployment.yaml @@ -43,7 +43,7 @@ spec: args: - --watch-all-namespaces - --log-level=info - - --log-json + - --log-encoding=json - --enable-leader-election readinessProbe: httpGet: diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index 1043fd4e..5790d3f2 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -4,23 +4,41 @@ kind: Role metadata: name: leader-election-role rules: -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - coordination.k8s.io - resources: - - leases/status - verbs: - - get - - update - - patch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - configmaps/status + verbs: + - get + - update + - patch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - apiGroups: + - "coordination.k8s.io" + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete diff --git a/controllers/kustomization_controller.go b/controllers/kustomization_controller.go index 48266b3f..4327c7ee 100644 --- a/controllers/kustomization_controller.go +++ b/controllers/kustomization_controller.go @@ -25,29 +25,26 @@ import ( "os" "os/exec" "path/filepath" - "sigs.k8s.io/controller-runtime/pkg/predicate" "strings" "time" securejoin "github.com/cyphar/filepath-securejoin" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/clientcmd" kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/tools/reference" "sigs.k8s.io/cli-utils/pkg/kstatus/polling" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/krusty" @@ -154,7 +151,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques if apierrors.IsNotFound(err) { msg := "Source not found" kustomization = kustomizev1.KustomizationNotReady(kustomization, "", kustomizev1.ArtifactFailedReason, msg) - if err := r.updateStatus(ctx, req, kustomization.Status); err != nil { + if err := r.patchStatus(ctx, req, kustomization.Status); err != nil { log.Error(err, "unable to update status for source not found") return ctrl.Result{Requeue: true}, err } @@ -171,7 +168,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques if source.GetArtifact() == nil { msg := "Source is not ready, artifact not found" kustomization = kustomizev1.KustomizationNotReady(kustomization, "", kustomizev1.ArtifactFailedReason, msg) - if err := r.updateStatus(ctx, req, kustomization.Status); err != nil { + if err := r.patchStatus(ctx, req, kustomization.Status); err != nil { log.Error(err, "unable to update status for artifact not found") return ctrl.Result{Requeue: true}, err } @@ -186,7 +183,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques if err := r.checkDependencies(kustomization); err != nil { kustomization = kustomizev1.KustomizationNotReady( kustomization, source.GetArtifact().Revision, meta.DependencyNotReadyReason, err.Error()) - if err := r.updateStatus(ctx, req, kustomization.Status); err != nil { + if err := r.patchStatus(ctx, req, kustomization.Status); err != nil { log.Error(err, "unable to update status for dependency not ready") return ctrl.Result{Requeue: true}, err } @@ -212,7 +209,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques // set the reconciliation status to progressing kustomization = kustomizev1.KustomizationProgressing(kustomization) - if err := r.updateStatus(ctx, req, kustomization.Status); err != nil { + if err := r.patchStatus(ctx, req, kustomization.Status); err != nil { (logr.FromContext(ctx)).Error(err, "unable to update status to progressing") return ctrl.Result{Requeue: true}, err } @@ -226,7 +223,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // update status - if err := r.updateStatus(ctx, req, reconciledKustomization.Status); err != nil { + if err := r.patchStatus(ctx, req, reconciledKustomization.Status); err != nil { log.Error(err, "unable to update status after reconciliation") return ctrl.Result{Requeue: true}, err } @@ -327,37 +324,38 @@ func (r *KustomizationReconciler) reconcile( ), err } - // dry-run apply - err = r.validate(kustomization, dirPath) + // create any necessary kube-clients for impersonation + impersonation := NewKustomizeImpersonation(kustomization, r.Client, r.StatusPoller, dirPath) + client, statusPoller, err := impersonation.GetClient(ctx) if err != nil { return kustomizev1.KustomizationNotReady( kustomization, source.GetArtifact().Revision, - kustomizev1.ValidationFailedReason, + meta.ReconciliationFailedReason, err.Error(), - ), err + ), fmt.Errorf("failed to build kube client: %w", err) } - // apply - changeSet, err := r.applyWithRetry(ctx, kustomization, source.GetArtifact().Revision, dirPath, 5*time.Second) + // dry-run apply + err = r.validate(ctx, kustomization, impersonation, dirPath) if err != nil { return kustomizev1.KustomizationNotReady( kustomization, source.GetArtifact().Revision, - meta.ReconciliationFailedReason, + kustomizev1.ValidationFailedReason, err.Error(), ), err } - // create any necessary kube-clients - client, statusPoller, err := r.newKustomizationClient(kustomization) + // apply + changeSet, err := r.applyWithRetry(ctx, kustomization, impersonation, source.GetArtifact().Revision, dirPath, 5*time.Second) if err != nil { return kustomizev1.KustomizationNotReady( kustomization, source.GetArtifact().Revision, meta.ReconciliationFailedReason, err.Error(), - ), fmt.Errorf("Failed to build kube client for Kustomization: %w", err) + ), err } // prune @@ -392,6 +390,30 @@ func (r *KustomizationReconciler) reconcile( ), nil } +func (r *KustomizationReconciler) checkDependencies(kustomization kustomizev1.Kustomization) error { + for _, d := range kustomization.Spec.DependsOn { + if d.Namespace == "" { + d.Namespace = kustomization.GetNamespace() + } + dName := types.NamespacedName(d) + var k kustomizev1.Kustomization + err := r.Get(context.Background(), dName, &k) + if err != nil { + return fmt.Errorf("unable to get '%s' dependency: %w", dName, err) + } + + if len(k.Status.Conditions) == 0 || k.Generation != k.Status.ObservedGeneration { + return fmt.Errorf("dependency '%s' is not ready", dName) + } + + if !apimeta.IsStatusConditionTrue(k.Status.Conditions, meta.ReadyCondition) { + return fmt.Errorf("dependency '%s' is not ready", dName) + } + } + + return nil +} + func (r *KustomizationReconciler) download(kustomization kustomizev1.Kustomization, url string, tmpDir string) error { timeout := kustomization.GetTimeout() + (time.Second * 1) ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -522,56 +544,27 @@ func (r *KustomizationReconciler) build(kustomization kustomizev1.Kustomization, return kustomizev1.NewSnapshot(resources, checksum) } -func (r *KustomizationReconciler) reconcileDelete(ctx context.Context, kustomization kustomizev1.Kustomization) (ctrl.Result, error) { - if kustomization.Spec.Prune && !kustomization.Spec.Suspend { - // create any necessary kube-clients - client, _, err := r.newKustomizationClient(kustomization) - if err != nil { - err = fmt.Errorf("failed to build kube client for Kustomization: %w", err) - (logr.FromContext(ctx)).Error(err, "Unable to prune for finalizer") - return ctrl.Result{}, err - } - if err := r.prune(ctx, client, kustomization, ""); err != nil { - r.event(ctx, kustomization, kustomization.Status.LastAppliedRevision, events.EventSeverityError, "pruning for deleted resource failed", nil) - // Return the error so we retry the failed garbage collection - return ctrl.Result{}, err - } - } - - // Record deleted status - r.recordReadiness(ctx, kustomization) - - // Remove our finalizer from the list and update it - controllerutil.RemoveFinalizer(&kustomization, kustomizev1.KustomizationFinalizer) - if err := r.Update(ctx, &kustomization); err != nil { - return ctrl.Result{}, err - } - - // Stop reconciliation as the object is being deleted - return ctrl.Result{}, nil -} - -func (r *KustomizationReconciler) validate(kustomization kustomizev1.Kustomization, dirPath string) error { +func (r *KustomizationReconciler) validate(ctx context.Context, kustomization kustomizev1.Kustomization, imp *KustomizeImpersonation, dirPath string) error { if kustomization.Spec.Validation == "" || kustomization.Spec.Validation == "none" { return nil } timeout := kustomization.GetTimeout() + (time.Second * 1) - ctx, cancel := context.WithTimeout(context.Background(), timeout) + applyCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() cmd := fmt.Sprintf("cd %s && kubectl apply -f %s.yaml --timeout=%s --dry-run=%s --cache-dir=/tmp", dirPath, kustomization.GetUID(), kustomization.GetTimeout().String(), kustomization.Spec.Validation) if kustomization.Spec.KubeConfig != nil { - kubeConfig, err := r.writeKubeConfig(kustomization, dirPath) + kubeConfig, err := imp.WriteKubeConfig(ctx) if err != nil { return err } cmd = fmt.Sprintf("%s --kubeconfig=%s", cmd, kubeConfig) } - command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd) + command := exec.CommandContext(applyCtx, "/bin/sh", "-c", cmd) output, err := command.CombinedOutput() if err != nil { if errors.Is(err, context.DeadlineExceeded) { @@ -582,92 +575,7 @@ func (r *KustomizationReconciler) validate(kustomization kustomizev1.Kustomizati return nil } -func (r *KustomizationReconciler) writeKubeConfig(kustomization kustomizev1.Kustomization, dirPath string) (string, error) { - secretName := types.NamespacedName{ - Namespace: kustomization.GetNamespace(), - Name: kustomization.Spec.KubeConfig.SecretRef.Name, - } - - kubeConfig, err := r.getKubeConfig(kustomization) - if err != nil { - return "", err - } - - f, err := ioutil.TempFile(dirPath, "kubeconfig") - defer f.Close() - if err != nil { - return "", fmt.Errorf("unable to write KubeConfig secret '%s' to storage: %w", secretName.String(), err) - } - if _, err := f.Write(kubeConfig); err != nil { - return "", fmt.Errorf("unable to write KubeConfig secret '%s' to storage: %w", secretName.String(), err) - } - return f.Name(), nil -} - -func (r *KustomizationReconciler) getKubeConfig(kustomization kustomizev1.Kustomization) ([]byte, error) { - timeout := kustomization.GetTimeout() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - secretName := types.NamespacedName{ - Namespace: kustomization.GetNamespace(), - Name: kustomization.Spec.KubeConfig.SecretRef.Name, - } - - var secret corev1.Secret - if err := r.Get(ctx, secretName, &secret); err != nil { - return nil, fmt.Errorf("unable to read KubeConfig secret '%s' error: %w", secretName.String(), err) - } - - kubeConfig, ok := secret.Data["value"] - if !ok { - return nil, fmt.Errorf("KubeConfig secret '%s' doesn't contain a 'value' key ", secretName.String()) - } - - return kubeConfig, nil -} - -func (r *KustomizationReconciler) getServiceAccountToken(kustomization kustomizev1.Kustomization) (string, error) { - namespacedName := types.NamespacedName{ - Namespace: kustomization.Namespace, - Name: kustomization.Spec.ServiceAccountName, - } - - var serviceAccount corev1.ServiceAccount - err := r.Client.Get(context.TODO(), namespacedName, &serviceAccount) - if err != nil { - return "", err - } - - secretName := types.NamespacedName{ - Namespace: kustomization.Namespace, - Name: kustomization.Spec.ServiceAccountName, - } - - for _, secret := range serviceAccount.Secrets { - if strings.HasPrefix(secret.Name, fmt.Sprintf("%s-token", serviceAccount.Name)) { - secretName.Name = secret.Name - break - } - } - - var secret corev1.Secret - err = r.Client.Get(context.TODO(), secretName, &secret) - if err != nil { - return "", err - } - - var token string - if data, ok := secret.Data["token"]; ok { - token = string(data) - } else { - return "", fmt.Errorf("the service account secret '%s' does not containt a token", secretName.String()) - } - - return token, nil -} - -func (r *KustomizationReconciler) apply(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (string, error) { +func (r *KustomizationReconciler) apply(ctx context.Context, kustomization kustomizev1.Kustomization, imp *KustomizeImpersonation, dirPath string) (string, error) { start := time.Now() timeout := kustomization.GetTimeout() + (time.Second * 1) applyCtx, cancel := context.WithTimeout(ctx, timeout) @@ -678,7 +586,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context, kustomization kusto dirPath, fieldManager, kustomization.GetUID(), kustomization.Spec.Interval.Duration.String()) if kustomization.Spec.KubeConfig != nil { - kubeConfig, err := r.writeKubeConfig(kustomization, dirPath) + kubeConfig, err := imp.WriteKubeConfig(ctx) if err != nil { return "", err } @@ -686,7 +594,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context, kustomization kusto } else { // impersonate SA if kustomization.Spec.ServiceAccountName != "" { - saToken, err := r.getServiceAccountToken(kustomization) + saToken, err := imp.GetServiceAccountToken(ctx) if err != nil { return "", fmt.Errorf("service account impersonation failed: %w", err) } @@ -725,15 +633,15 @@ func (r *KustomizationReconciler) apply(ctx context.Context, kustomization kusto return changeSet, nil } -func (r *KustomizationReconciler) applyWithRetry(ctx context.Context, kustomization kustomizev1.Kustomization, revision, dirPath string, delay time.Duration) (string, error) { - changeSet, err := r.apply(ctx, kustomization, dirPath) +func (r *KustomizationReconciler) applyWithRetry(ctx context.Context, kustomization kustomizev1.Kustomization, imp *KustomizeImpersonation, revision, dirPath string, delay time.Duration) (string, error) { + changeSet, err := r.apply(ctx, kustomization, imp, dirPath) if err != nil { // retry apply due to CRD/CR race if strings.Contains(err.Error(), "could not find the requested resource") || strings.Contains(err.Error(), "no matches for kind") { (logr.FromContext(ctx)).Info("retrying apply", "error", err.Error()) time.Sleep(delay) - if changeSet, err := r.apply(ctx, kustomization, dirPath); err != nil { + if changeSet, err := r.apply(ctx, kustomization, imp, dirPath); err != nil { return "", err } else { if changeSet != "" { @@ -795,28 +703,34 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context, statusPoller return nil } -func (r *KustomizationReconciler) checkDependencies(kustomization kustomizev1.Kustomization) error { - for _, d := range kustomization.Spec.DependsOn { - if d.Namespace == "" { - d.Namespace = kustomization.GetNamespace() - } - dName := types.NamespacedName(d) - var k kustomizev1.Kustomization - err := r.Get(context.Background(), dName, &k) +func (r *KustomizationReconciler) reconcileDelete(ctx context.Context, kustomization kustomizev1.Kustomization) (ctrl.Result, error) { + if kustomization.Spec.Prune && !kustomization.Spec.Suspend { + // create any necessary kube-clients + imp := NewKustomizeImpersonation(kustomization, r.Client, r.StatusPoller, "") + client, _, err := imp.GetClient(ctx) if err != nil { - return fmt.Errorf("unable to get '%s' dependency: %w", dName, err) + err = fmt.Errorf("failed to build kube client for Kustomization: %w", err) + (logr.FromContext(ctx)).Error(err, "Unable to prune for finalizer") + return ctrl.Result{}, err } - - if len(k.Status.Conditions) == 0 || k.Generation != k.Status.ObservedGeneration { - return fmt.Errorf("dependency '%s' is not ready", dName) + if err := r.prune(ctx, client, kustomization, ""); err != nil { + r.event(ctx, kustomization, kustomization.Status.LastAppliedRevision, events.EventSeverityError, "pruning for deleted resource failed", nil) + // Return the error so we retry the failed garbage collection + return ctrl.Result{}, err } + } - if !apimeta.IsStatusConditionTrue(k.Status.Conditions, meta.ReadyCondition) { - return fmt.Errorf("dependency '%s' is not ready", dName) - } + // Record deleted status + r.recordReadiness(ctx, kustomization) + + // Remove our finalizer from the list and update it + controllerutil.RemoveFinalizer(&kustomization, kustomizev1.KustomizationFinalizer) + if err := r.Update(ctx, &kustomization); err != nil { + return ctrl.Result{}, err } - return nil + // Stop reconciliation as the object is being deleted + return ctrl.Result{}, nil } func (r *KustomizationReconciler) event(ctx context.Context, kustomization kustomizev1.Kustomization, revision, severity, msg string, metadata map[string]string) { @@ -870,42 +784,7 @@ func (r *KustomizationReconciler) recordReadiness(ctx context.Context, kustomiza } } -func (r *KustomizationReconciler) newKustomizationClient(kustomization kustomizev1.Kustomization) (client.Client, *polling.StatusPoller, error) { - if kustomization.Spec.KubeConfig == nil { - // TODO: implement impersonation overrides for in-cluster - return r.Client, r.StatusPoller, nil - } - - kubeConfigBytes, err := r.getKubeConfig(kustomization) - if err != nil { - return nil, nil, err - } - - restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigBytes) - if err != nil { - return nil, nil, err - } - - // TODO: implement impersonation overrides on the target cluster restConfig - - restMapper, err := apiutil.NewDynamicRESTMapper(restConfig) - if err != nil { - return nil, nil, err - } - - // this client does not have a cache like the normal controller-runtime default client - // this is intentional but one could be added - client, err := client.New(restConfig, client.Options{Mapper: restMapper}) - if err != nil { - return nil, nil, err - } - - statusPoller := polling.NewStatusPoller(client, restMapper) - - return client, statusPoller, err -} - -func (r *KustomizationReconciler) updateStatus(ctx context.Context, req ctrl.Request, newStatus kustomizev1.KustomizationStatus) error { +func (r *KustomizationReconciler) patchStatus(ctx context.Context, req ctrl.Request, newStatus kustomizev1.KustomizationStatus) error { var kustomization kustomizev1.Kustomization if err := r.Get(ctx, req.NamespacedName, &kustomization); err != nil { return err diff --git a/controllers/kustomization_impersonation.go b/controllers/kustomization_impersonation.go new file mode 100644 index 00000000..00b826b5 --- /dev/null +++ b/controllers/kustomization_impersonation.go @@ -0,0 +1,169 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + "io/ioutil" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1" +) + +type KustomizeImpersonation struct { + workdir string + kustomization kustomizev1.Kustomization + statusPoller *polling.StatusPoller + client.Client +} + +func NewKustomizeImpersonation( + kustomization kustomizev1.Kustomization, + kubeClient client.Client, + statusPoller *polling.StatusPoller, + workdir string) *KustomizeImpersonation { + return &KustomizeImpersonation{ + workdir: workdir, + kustomization: kustomization, + statusPoller: statusPoller, + Client: kubeClient, + } +} + +func (ki *KustomizeImpersonation) GetServiceAccountToken(ctx context.Context) (string, error) { + namespacedName := types.NamespacedName{ + Namespace: ki.kustomization.Namespace, + Name: ki.kustomization.Spec.ServiceAccountName, + } + + var serviceAccount corev1.ServiceAccount + err := ki.Client.Get(ctx, namespacedName, &serviceAccount) + if err != nil { + return "", err + } + + secretName := types.NamespacedName{ + Namespace: ki.kustomization.Namespace, + Name: ki.kustomization.Spec.ServiceAccountName, + } + + for _, secret := range serviceAccount.Secrets { + if strings.HasPrefix(secret.Name, fmt.Sprintf("%s-token", serviceAccount.Name)) { + secretName.Name = secret.Name + break + } + } + + var secret corev1.Secret + err = ki.Client.Get(ctx, secretName, &secret) + if err != nil { + return "", err + } + + var token string + if data, ok := secret.Data["token"]; ok { + token = string(data) + } else { + return "", fmt.Errorf("the service account secret '%s' does not containt a token", secretName.String()) + } + + return token, nil +} + +func (ki *KustomizeImpersonation) GetClient(ctx context.Context) (client.Client, *polling.StatusPoller, error) { + if ki.kustomization.Spec.KubeConfig == nil { + // TODO: implement impersonation overrides for in-cluster + return ki.Client, ki.statusPoller, nil + } + + kubeConfigBytes, err := ki.getKubeConfig(ctx) + if err != nil { + return nil, nil, err + } + + restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigBytes) + if err != nil { + return nil, nil, err + } + + // TODO: implement impersonation overrides on the target cluster restConfig + + restMapper, err := apiutil.NewDynamicRESTMapper(restConfig) + if err != nil { + return nil, nil, err + } + + // this client does not have a cache like the normal controller-runtime default client + // this is intentional but one could be added + client, err := client.New(restConfig, client.Options{Mapper: restMapper}) + if err != nil { + return nil, nil, err + } + + statusPoller := polling.NewStatusPoller(client, restMapper) + + return client, statusPoller, err +} + +func (ki *KustomizeImpersonation) WriteKubeConfig(ctx context.Context) (string, error) { + secretName := types.NamespacedName{ + Namespace: ki.kustomization.GetNamespace(), + Name: ki.kustomization.Spec.KubeConfig.SecretRef.Name, + } + + kubeConfig, err := ki.getKubeConfig(ctx) + if err != nil { + return "", err + } + + f, err := ioutil.TempFile(ki.workdir, "kubeconfig") + defer f.Close() + if err != nil { + return "", fmt.Errorf("unable to write KubeConfig secret '%s' to storage: %w", secretName.String(), err) + } + if _, err := f.Write(kubeConfig); err != nil { + return "", fmt.Errorf("unable to write KubeConfig secret '%s' to storage: %w", secretName.String(), err) + } + return f.Name(), nil +} + +func (ki *KustomizeImpersonation) getKubeConfig(ctx context.Context) ([]byte, error) { + secretName := types.NamespacedName{ + Namespace: ki.kustomization.GetNamespace(), + Name: ki.kustomization.Spec.KubeConfig.SecretRef.Name, + } + + var secret corev1.Secret + if err := ki.Get(ctx, secretName, &secret); err != nil { + return nil, fmt.Errorf("unable to read KubeConfig secret '%s' error: %w", secretName.String(), err) + } + + kubeConfig, ok := secret.Data["value"] + if !ok { + return nil, fmt.Errorf("KubeConfig secret '%s' doesn't contain a 'value' key ", secretName.String()) + } + + return kubeConfig, nil +}