diff --git a/config/rbac/rbac_role.yaml b/config/rbac/rbac_role.yaml index 3b248988a96e..317c6f54dce2 100644 --- a/config/rbac/rbac_role.yaml +++ b/config/rbac/rbac_role.yaml @@ -101,3 +101,11 @@ rules: - update - patch - delete +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch diff --git a/pkg/controller/BUILD.bazel b/pkg/controller/BUILD.bazel index 8f075625a775..0808edfe87a0 100644 --- a/pkg/controller/BUILD.bazel +++ b/pkg/controller/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "add_machinedeployment.go", "add_machineset.go", "add_node.go", + "add_noderef.go", "controller.go", ], importpath = "sigs.k8s.io/cluster-api/pkg/controller", @@ -14,6 +15,7 @@ go_library( "//pkg/controller/machinedeployment:go_default_library", "//pkg/controller/machineset:go_default_library", "//pkg/controller/node:go_default_library", + "//pkg/controller/noderef:go_default_library", "//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library", ], ) diff --git a/pkg/controller/add_noderef.go b/pkg/controller/add_noderef.go new file mode 100644 index 000000000000..4a5d4cad7c1d --- /dev/null +++ b/pkg/controller/add_noderef.go @@ -0,0 +1,26 @@ +/* +Copyright 2019 The Kubernetes 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 controller + +import ( + "sigs.k8s.io/cluster-api/pkg/controller/noderef" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, noderef.Add) +} diff --git a/pkg/controller/noderef/BUILD.bazel b/pkg/controller/noderef/BUILD.bazel new file mode 100644 index 000000000000..91e92e9ebc4a --- /dev/null +++ b/pkg/controller/noderef/BUILD.bazel @@ -0,0 +1,51 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["noderef_controller.go"], + importpath = "sigs.k8s.io/cluster-api/pkg/controller/noderef", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/cluster/v1alpha1:go_default_library", + "//pkg/controller/noderefutil:go_default_library", + "//pkg/controller/remote:go_default_library", + "//vendor/github.com/pkg/errors:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + "//vendor/k8s.io/klog:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/controller:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/handler:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/source:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "noderef_controller_suite_test.go", + "noderef_controller_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//pkg/apis:go_default_library", + "//pkg/apis/cluster/v1alpha1:go_default_library", + "//vendor/github.com/onsi/gomega:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/envtest:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library", + "//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library", + ], +) diff --git a/pkg/controller/noderef/noderef_controller.go b/pkg/controller/noderef/noderef_controller.go new file mode 100644 index 000000000000..883aebecac2d --- /dev/null +++ b/pkg/controller/noderef/noderef_controller.go @@ -0,0 +1,220 @@ +/* +Copyright 2019 The Kubernetes 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 noderef + +import ( + "context" + "time" + + "github.com/pkg/errors" + apicorev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/record" + "k8s.io/klog" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/controller/noderefutil" + "sigs.k8s.io/cluster-api/pkg/controller/remote" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// controllerName is the name of this controller +const controllerName = "machinedeployment-controller" + +var ( + ErrNodeNotFound = errors.New("cannot find node with maching ProviderID") +) + +// Add creates a new NodeRef Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileNodeRef{Client: mgr.GetClient(), scheme: mgr.GetScheme(), recorder: mgr.GetRecorder(controllerName)} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("noderef-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to Machines. + return c.Watch(&source.Kind{Type: &v1alpha1.Machine{}}, &handler.EnqueueRequestForObject{}) +} + +var _ reconcile.Reconciler = &ReconcileNodeRef{} + +// ReconcileNodeRef reconciles a Machine object. +type ReconcileNodeRef struct { + client.Client + scheme *runtime.Scheme + recorder record.EventRecorder +} + +// Reconcile watches Machines. +// +kubebuilder:rbac:groups=,resources=secrets,verbs=get;list;watch +func (r *ReconcileNodeRef) Reconcile(request reconcile.Request) (reconcile.Result, error) { + klog.Infof("Reconcile request for Machine %q in namespace %q", request.Name, request.Namespace) + ctx := context.Background() + + // Fetch the NodeRef instance + machine := &v1alpha1.Machine{} + err := r.Get(ctx, request.NamespacedName, machine) + if err != nil { + if apierrors.IsNotFound(err) { + klog.V(2).Infof("Machine %q in namespace %q is not found, won't reconcile", machine.Name, machine.Namespace) + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + + // Check that the Machine hasn't been deleted or in the process. + if !machine.DeletionTimestamp.IsZero() { + klog.V(2).Infof("Machine %q in namespace %q has been deleted, won't reconcile", machine.Name, machine.Namespace) + return reconcile.Result{}, nil + } + + // Check that the Machine doesn't already have a NodeRef. + if machine.Status.NodeRef != nil { + klog.V(2).Infof("Machine %q in namespace %q already has a NodeRef, won't reconcile", machine.Name, machine.Namespace) + return reconcile.Result{}, nil + } + + // Check that the Machine has a cluster label. + if machine.Labels[v1alpha1.MachineClusterLabelName] == "" { + klog.V(2).Infof("Machine %q in namespace %q doesn't specify %q label, won't reconcile", machine.Name, machine.Namespace, + v1alpha1.MachineClusterLabelName) + return reconcile.Result{}, nil + } + + // Check that the Machine has a valid ProviderID. + if machine.Spec.ProviderID == nil || *machine.Spec.ProviderID == "" { + klog.Warningf("Machine %q in namespace %q doesn't have a valid ProviderID, retrying later", machine.Name, machine.Namespace) + return reconcile.Result{RequeueAfter: 30 * time.Second}, nil + } + + result, err := r.reconcile(ctx, machine) + if err != nil { + klog.Errorf("Failed to assign NodeRef to Machine %q: %v", request.NamespacedName, err) + r.recorder.Eventf(machine, apicorev1.EventTypeWarning, "NodeRefReconcileError", "%v", err) + return result, err + } + + klog.Infof("Set Machine's (%q in namespace %q) NodeRef to %q", machine.Name, machine.Namespace, machine.Status.NodeRef.Name) + return result, nil +} + +func (r ReconcileNodeRef) reconcile(ctx context.Context, machine *v1alpha1.Machine) (reconcile.Result, error) { + providerID, err := noderefutil.NewProviderID(*machine.Spec.ProviderID) + if err != nil { + return reconcile.Result{}, err + } + + cluster, err := r.getCluster(ctx, machine) + if err != nil { + return reconcile.Result{}, err + } + + clusterClient, err := remote.NewClusterClient(r.Client, cluster) + if err != nil { + return reconcile.Result{}, err + } + + corev1Client, err := clusterClient.CoreV1() + if err != nil { + return reconcile.Result{}, err + } + + // Get the Node reference. + nodeRef, err := r.getNodeReference(corev1Client, providerID) + if err != nil { + if err == ErrNodeNotFound { + klog.Warningf("Cannot find a matching Node for Machine %q in namespace %q, retrying later", machine.Name, machine.Namespace) + return reconcile.Result{RequeueAfter: 10 * time.Second}, nil + } + return reconcile.Result{}, err + } + + // Update Machine. + machine.Status.NodeRef = nodeRef + if err := r.Client.Status().Update(ctx, machine); err != nil { + return reconcile.Result{}, err + } + + return reconcile.Result{}, nil +} + +func (r *ReconcileNodeRef) getCluster(ctx context.Context, machine *v1alpha1.Machine) (*v1alpha1.Cluster, error) { + cluster := &v1alpha1.Cluster{} + key := client.ObjectKey{ + Namespace: machine.Namespace, + Name: machine.Labels[v1alpha1.MachineClusterLabelName], + } + + if err := r.Client.Get(ctx, key, cluster); err != nil { + return nil, err + } + + return cluster, nil +} + +func (r *ReconcileNodeRef) getNodeReference(client corev1.NodesGetter, providerID *noderefutil.ProviderID) (*apicorev1.ObjectReference, error) { + listOpt := metav1.ListOptions{} + + for { + nodeList, err := client.Nodes().List(listOpt) + if err != nil { + return nil, err + } + + for _, node := range nodeList.Items { + nodeProviderID, err := noderefutil.NewProviderID(node.Spec.ProviderID) + if err != nil { + continue + } + + if providerID.Equals(nodeProviderID) { + return &apicorev1.ObjectReference{ + Kind: node.Kind, + APIVersion: node.APIVersion, + Name: node.Name, + UID: node.UID, + }, nil + } + } + + listOpt.Continue = nodeList.Continue + if listOpt.Continue == "" { + break + } + } + + return nil, ErrNodeNotFound +} diff --git a/pkg/controller/noderef/noderef_controller_suite_test.go b/pkg/controller/noderef/noderef_controller_suite_test.go new file mode 100644 index 000000000000..eeb7833edd64 --- /dev/null +++ b/pkg/controller/noderef/noderef_controller_suite_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2019 The Kubernetes 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 noderef + +import ( + stdlog "log" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/cluster-api/pkg/apis" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var cfg *rest.Config + +func TestMain(m *testing.M) { + t := &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")}, + } + apis.AddToScheme(scheme.Scheme) + + var err error + if cfg, err = t.Start(); err != nil { + stdlog.Fatal(err) + } + + code := m.Run() + t.Stop() + os.Exit(code) +} + +// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and +// writes the request to requests after Reconcile is finished. +func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) { + requests := make(chan reconcile.Request) + fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) { + result, err := inner.Reconcile(req) + requests <- req + return result, err + }) + return fn, requests +} + +// StartTestManager adds recFn +func StartTestManager(mgr manager.Manager, g *gomega.GomegaWithT) (chan struct{}, *sync.WaitGroup) { + stop := make(chan struct{}) + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + g.Expect(mgr.Start(stop)).NotTo(gomega.HaveOccurred()) + }() + return stop, wg +} diff --git a/pkg/controller/noderef/noderef_controller_test.go b/pkg/controller/noderef/noderef_controller_test.go new file mode 100644 index 000000000000..b0f24531c75a --- /dev/null +++ b/pkg/controller/noderef/noderef_controller_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2019 The Kubernetes 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 noderef + +import ( + "testing" + "time" + + "github.com/onsi/gomega" + "golang.org/x/net/context" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var c client.Client + +var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo", Namespace: "default"}} + +const timeout = time.Second * 5 + +func TestReconcile(t *testing.T) { + g := gomega.NewGomegaWithT(t) + instance := &v1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + } + + // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a + // channel when it is finished. + mgr, err := manager.New(cfg, manager.Options{}) + g.Expect(err).NotTo(gomega.HaveOccurred()) + c = mgr.GetClient() + + recFn, requests := SetupTestReconcile(newReconciler(mgr)) + g.Expect(add(mgr, recFn)).NotTo(gomega.HaveOccurred()) + + stopMgr, mgrStopped := StartTestManager(mgr, g) + + defer func() { + close(stopMgr) + mgrStopped.Wait() + }() + + // Create the Machine object and expect the Reconcile + err = c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + t.Logf("failed to create object, got an invalid object error: %v", err) + return + } + g.Expect(err).NotTo(gomega.HaveOccurred()) + defer c.Delete(context.TODO(), instance) + g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest))) + +} diff --git a/pkg/controller/noderefutil/BUILD.bazel b/pkg/controller/noderefutil/BUILD.bazel index 7868e41e8245..a42c37409283 100644 --- a/pkg/controller/noderefutil/BUILD.bazel +++ b/pkg/controller/noderefutil/BUILD.bazel @@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["util.go"], + srcs = [ + "providerid.go", + "util.go", + ], importpath = "sigs.k8s.io/cluster-api/pkg/controller/noderefutil", visibility = ["//visibility:public"], deps = [ @@ -13,7 +16,10 @@ go_library( go_test( name = "go_default_test", - srcs = ["util_test.go"], + srcs = [ + "providerid_test.go", + "util_test.go", + ], embed = [":go_default_library"], deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/noderefutil/providerid.go b/pkg/controller/noderefutil/providerid.go new file mode 100644 index 000000000000..3d422434a177 --- /dev/null +++ b/pkg/controller/noderefutil/providerid.go @@ -0,0 +1,69 @@ +/* +Copyright 2019 The Kubernetes 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 noderefutil + +import ( + "errors" + "net/url" + "path" +) + +var ( + ErrEmptyProviderID = errors.New("providerID is empty") +) + +// ProviderID is a struct representation of a Kubernetes ProviderID. +// Format: cloudProvider://optional/segments/etc/id +type ProviderID struct { + value *url.URL +} + +// NewProviderID parses the input string and returns a new ProviderID. +func NewProviderID(id string) (*ProviderID, error) { + if id == "" { + return nil, ErrEmptyProviderID + } + + parsed, err := url.Parse(id) + if err != nil { + return nil, err + } + + return &ProviderID{ + value: parsed, + }, nil +} + +// CloudProvider returns the cloud provider portion of the ProviderID. +func (p *ProviderID) CloudProvider() string { + return p.value.Scheme +} + +// ID returns the identifier portion of the ProviderID. +func (p *ProviderID) ID() string { + return path.Base(p.value.Path) +} + +// Equals returns true if both the CloudProvider and ID match. +func (p *ProviderID) Equals(o *ProviderID) bool { + return p.CloudProvider() == o.CloudProvider() && p.ID() == o.ID() +} + +// String returns the string representation of this object. +func (p *ProviderID) String() string { + return p.value.String() +} diff --git a/pkg/controller/noderefutil/providerid_test.go b/pkg/controller/noderefutil/providerid_test.go new file mode 100644 index 000000000000..f5d96cb8197e --- /dev/null +++ b/pkg/controller/noderefutil/providerid_test.go @@ -0,0 +1,45 @@ +/* +Copyright 2019 The Kubernetes 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 noderefutil + +import "testing" + +func TestNewProviderID(t *testing.T) { + input1 := "aws:///instance-id" + _, err := NewProviderID(input1) + if err != nil { + t.Fatalf("Expected no errors, got %v", err) + } +} + +func TestProviderIDEquals(t *testing.T) { + input1 := "aws:///instance-id1" + parsed1, err := NewProviderID(input1) + if err != nil { + t.Fatalf("Expected no errors, got %v", err) + } + + input2 := "aws:///us-west-1/instance-id1" + parsed2, err := NewProviderID(input2) + if err != nil { + t.Fatalf("Expected no errors, got %v", err) + } + + if !parsed1.Equals(parsed2) { + t.Fatal("Expected ProviderIDs to be equal") + } +} diff --git a/pkg/controller/remote/cluster.go b/pkg/controller/remote/cluster.go index 073ba89ce44c..e5498136c9d8 100644 --- a/pkg/controller/remote/cluster.go +++ b/pkg/controller/remote/cluster.go @@ -25,6 +25,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +// ClusterClientInterface is an interface encapsulating methods +// to access a remote cluster. +type ClusterClientInterface interface { + RESTConfig() *restclient.Config + CoreV1() (corev1.CoreV1Interface, error) +} + // ClusterClient is a helper struct to connect to remote workload clusters. type ClusterClient struct { restConfig *restclient.Config @@ -32,16 +39,16 @@ type ClusterClient struct { } // NewClusterClient creates a new ClusterClient instance. -func NewClusterClient(c client.Client, cluster *v1alpha1.Cluster) (*ClusterClient, error) { +func NewClusterClient(c client.Client, cluster *v1alpha1.Cluster) (ClusterClientInterface, error) { secret, err := GetKubeConfigSecret(c, cluster.Name, cluster.Namespace) if err != nil { return nil, errors.Wrapf(err, "failed to retrieve kubeconfig secret for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } - kubeconfig, err := DecodeKubeConfigSecret(secret) + kubeconfig, err := KubeConfigFromSecret(secret) if err != nil { - return nil, errors.Wrapf(err, "failed to decode kubeconfig secret for Cluster %q in namespace %q", + return nil, errors.Wrapf(err, "failed to get kubeconfig from secret for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } diff --git a/pkg/controller/remote/util.go b/pkg/controller/remote/util.go index 13396c4e9a60..72005aef0fd5 100644 --- a/pkg/controller/remote/util.go +++ b/pkg/controller/remote/util.go @@ -18,7 +18,6 @@ package remote import ( "context" - "encoding/base64" "fmt" "github.com/pkg/errors" @@ -61,17 +60,11 @@ func GetKubeConfigSecret(c client.Client, cluster, namespace string) (*corev1.Se return secret, nil } -// DecodeKubeConfigSecret uses the Secret to retrieve and decode the data. -func DecodeKubeConfigSecret(secret *corev1.Secret) ([]byte, error) { - encodedKubeconfig, ok := secret.Data[kubeconfigSecretKey] +// KubeConfigFromSecret uses the Secret to retrieve the KubeConfig. +func KubeConfigFromSecret(secret *corev1.Secret) (out []byte, err error) { + data, ok := secret.Data[kubeconfigSecretKey] if !ok { return nil, ErrSecretMissingValue } - - kubeconfig, err := base64.StdEncoding.DecodeString(string(encodedKubeconfig)) - if err != nil { - return nil, err - } - - return kubeconfig, nil + return data, nil } diff --git a/pkg/controller/remote/util_test.go b/pkg/controller/remote/util_test.go index 1ea60afac43e..5578bc00fdcd 100644 --- a/pkg/controller/remote/util_test.go +++ b/pkg/controller/remote/util_test.go @@ -17,7 +17,6 @@ limitations under the License. package remote import ( - "encoding/base64" "reflect" "testing" @@ -54,7 +53,7 @@ users: Namespace: "test", }, Data: map[string][]byte{ - kubeconfigSecretKey: []byte(base64.StdEncoding.EncodeToString([]byte(validKubeConfig))), + kubeconfigSecretKey: []byte(validKubeConfig), }, } @@ -81,9 +80,9 @@ func TestGetKubeConfigSecret(t *testing.T) { } } -func TestDecodeKubeConfigSecret(t *testing.T) { +func TestKubeConfigFromSecret(t *testing.T) { t.Run("with valid secret", func(t *testing.T) { - out, err := DecodeKubeConfigSecret(validSecret) + out, err := KubeConfigFromSecret(validSecret) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -92,11 +91,4 @@ func TestDecodeKubeConfigSecret(t *testing.T) { t.Fatalf("Expected decoded KubeConfig to match input") } }) - - t.Run("with invalid secret", func(t *testing.T) { - _, err := DecodeKubeConfigSecret(invalidSecret) - if err == nil { - t.Fatalf("Expected error, got nil") - } - }) }