From 63e60121332c277381a0250036f1dfbfc3ef1e75 Mon Sep 17 00:00:00 2001 From: Vladislav Kuzmin Date: Thu, 11 Feb 2021 16:47:26 +0400 Subject: [PATCH] Mount kubeconfig to GenericContainer executor Change-Id: Ide647fc0cfd9d281d57eeeaf8b3f9c33f59e7fdf --- pkg/api/v1alpha1/genericcontainer_types.go | 8 +++ .../testdata/single/cluster-map.yaml | 9 +++ .../testdata/single/kustomization.yaml | 1 + pkg/phase/executors/container.go | 36 ++++++++++ pkg/phase/executors/container_test.go | 67 +++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 pkg/container/testdata/single/cluster-map.yaml diff --git a/pkg/api/v1alpha1/genericcontainer_types.go b/pkg/api/v1alpha1/genericcontainer_types.go index 2db5800f5..6d22204f8 100644 --- a/pkg/api/v1alpha1/genericcontainer_types.go +++ b/pkg/api/v1alpha1/genericcontainer_types.go @@ -27,6 +27,14 @@ const ( GenericContainerTypeAirship GenericContainerType = "airship" // GenericContainerTypeKrm specifies that kustomize krm function will be used GenericContainerTypeKrm GenericContainerType = "krm" + // KubeConfigEnvKey uses as a key for kubeconfig env variable + KubeConfigEnvKey = "KUBECONFIG" + // KubeConfigPath is a path for mounted kubeconfig inside container + KubeConfigPath = "/kubeconfig" + // KubeConfigEnvKeyContext uses as a key for kubectl context env variable + KubeConfigEnvKeyContext = "KCTL_CONTEXT" + // KubeConfigEnv uses as a kubeconfig env variable + KubeConfigEnv = KubeConfigEnvKey + "=" + KubeConfigPath ) // +kubebuilder:object:root=true diff --git a/pkg/container/testdata/single/cluster-map.yaml b/pkg/container/testdata/single/cluster-map.yaml new file mode 100644 index 000000000..c146ecf9d --- /dev/null +++ b/pkg/container/testdata/single/cluster-map.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: airshipit.org/v1alpha1 +kind: ClusterMap +metadata: + labels: + airshipit.org/deploy-k8s: "false" + name: main-map +map: + testCluster: {} diff --git a/pkg/container/testdata/single/kustomization.yaml b/pkg/container/testdata/single/kustomization.yaml index 97a9721bd..dcd793cc7 100644 --- a/pkg/container/testdata/single/kustomization.yaml +++ b/pkg/container/testdata/single/kustomization.yaml @@ -1,2 +1,3 @@ resources: - secret.yaml + - cluster-map.yaml diff --git a/pkg/phase/executors/container.go b/pkg/phase/executors/container.go index 73d5ccdff..1979f5396 100644 --- a/pkg/phase/executors/container.go +++ b/pkg/phase/executors/container.go @@ -25,6 +25,7 @@ import ( "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/events" + "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/phase/ifc" ) @@ -40,6 +41,7 @@ type ContainerExecutor struct { ExecutorBundle document.Bundle ExecutorDocument document.Document Helper ifc.Helper + Options ifc.ExecutorConfig } // NewContainerExecutor creates instance of phase executor @@ -70,6 +72,7 @@ func NewContainerExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) { ClientFunc: container.NewClientV1Alpha1, Helper: cfg.Helper, Container: apiObj, + Options: cfg, }, nil } @@ -82,6 +85,14 @@ func (c *ContainerExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { Message: "starting generic container", }) + if c.Options.ClusterName != "" { + cleanup, err := c.SetKubeConfig() + if err != nil { + handleError(evtCh, err) + } + defer cleanup() + } + input, err := bundleReader(c.ExecutorBundle) if err != nil { // TODO move bundleFactory here, and make sure that if executorDoc is not defined, we dont fail @@ -121,6 +132,31 @@ func (c *ContainerExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { }) } +// SetKubeConfig adds env variable and mounts kubeconfig to container +func (c *ContainerExecutor) SetKubeConfig() (kubeconfig.Cleanup, error) { + clusterMap, err := c.Helper.ClusterMap() + if err != nil { + return nil, err + } + context, err := clusterMap.ClusterKubeconfigContext(c.Options.ClusterName) + if err != nil { + return nil, err + } + kubeConfigSrc, cleanup, err := c.Options.KubeConfig.GetFile() + if err != nil { + return nil, err + } + c.Container.Spec.StorageMounts = append(c.Container.Spec.StorageMounts, v1alpha1.StorageMount{ + MountType: "bind", + Src: kubeConfigSrc, + DstPath: v1alpha1.KubeConfigPath, + }) + envs := []string{v1alpha1.KubeConfigEnv, v1alpha1.KubeConfigEnvKeyContext + "=" + context} + c.Container.Spec.EnvVars = append(c.Container.Spec.EnvVars, envs...) + + return cleanup, nil +} + // bundleReader sets input for function func bundleReader(bundle document.Bundle) (io.Reader, error) { buf := &bytes.Buffer{} diff --git a/pkg/phase/executors/container_test.go b/pkg/phase/executors/container_test.go index bb21dbe41..a24d825d2 100644 --- a/pkg/phase/executors/container_test.go +++ b/pkg/phase/executors/container_test.go @@ -14,6 +14,7 @@ package executors_test import ( "fmt" + "io" "testing" "github.com/stretchr/testify/assert" @@ -24,6 +25,7 @@ import ( "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/events" + "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/phase/executors" "opendev.org/airship/airshipctl/pkg/phase/ifc" ) @@ -174,6 +176,14 @@ type: Opaque Container: tt.containerAPI, ClientFunc: tt.clientFunc, Helper: makeDefaultHelper(t, tt.targetPath, "../metadata.yaml"), + Options: ifc.ExecutorConfig{ + ClusterName: "testCluster", + KubeConfig: fakeKubeConfig{ + getFile: func() (string, kubeconfig.Cleanup, error) { + return "testPath", func() {}, nil + }, + }, + }, } ch := make(chan events.Event) @@ -199,3 +209,60 @@ type: Opaque }) } } + +func TestSetKubeConfig(t *testing.T) { + getFileErr := fmt.Errorf("failed to get file") + testCases := []struct { + name string + opts ifc.ExecutorConfig + expectedErr error + }{ + { + name: "Set valid kubeconfig", + opts: ifc.ExecutorConfig{ + ClusterName: "testCluster", + KubeConfig: fakeKubeConfig{ + getFile: func() (string, kubeconfig.Cleanup, error) { + return "testPath", func() {}, nil + }, + }, + }, + }, + { + name: "Failed to get kubeconfig file", + opts: ifc.ExecutorConfig{ + ClusterName: "testCluster", + KubeConfig: fakeKubeConfig{ + getFile: func() (string, kubeconfig.Cleanup, error) { + return "", func() {}, getFileErr + }, + }, + }, + expectedErr: getFileErr, + }, + } + + for _, tc := range testCases { + tt := tc + t.Run(tt.name, func(t *testing.T) { + e := executors.ContainerExecutor{ + Options: tt.opts, + Container: &v1alpha1.GenericContainer{}, + Helper: makeDefaultHelper(t, singleExecutorBundlePath, "../metadata.yaml"), + } + _, err := e.SetKubeConfig() + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +type fakeKubeConfig struct { + getFile func() (string, kubeconfig.Cleanup, error) +} + +func (k fakeKubeConfig) GetFile() (string, kubeconfig.Cleanup, error) { return k.getFile() } +func (k fakeKubeConfig) Write(_ io.Writer) error { return nil } +func (k fakeKubeConfig) WriteFile(path string) error { return nil } +func (k fakeKubeConfig) WriteTempFile(dumpRoot string) (string, kubeconfig.Cleanup, error) { + return k.getFile() +}