From 5f1b93604ec5e4553d71f60fe5000b16438f71ac Mon Sep 17 00:00:00 2001
From: Dmitry Ukov <dukov@mirantis.com>
Date: Fri, 26 Jun 2020 15:52:25 +0400
Subject: [PATCH] Introduce KubeConfig API object

Change-Id: I5894000377b1d22aac02c339ab1938ba35bcf651
---
 pkg/api/v1alpha1/groupversion_info.go     |  1 +
 pkg/api/v1alpha1/kubeconfig_types.go      | 29 ++++++++
 pkg/api/v1alpha1/zz_generated.deepcopy.go | 26 +++++++
 pkg/document/filesystem.go                |  6 ++
 pkg/k8s/utils/utils.go                    | 27 +++++++
 pkg/k8s/utils/utils_test.go               | 87 +++++++++++++++++++++++
 6 files changed, 176 insertions(+)
 create mode 100644 pkg/api/v1alpha1/kubeconfig_types.go

diff --git a/pkg/api/v1alpha1/groupversion_info.go b/pkg/api/v1alpha1/groupversion_info.go
index 15b902d47..c11f9ac35 100644
--- a/pkg/api/v1alpha1/groupversion_info.go
+++ b/pkg/api/v1alpha1/groupversion_info.go
@@ -44,6 +44,7 @@ func init() {
 		&Clusterctl{},
 		&Phase{},
 		&PhasePlan{},
+		&KubeConfig{},
 	)
 	_ = AddToScheme(Scheme) //nolint:errcheck
 }
diff --git a/pkg/api/v1alpha1/kubeconfig_types.go b/pkg/api/v1alpha1/kubeconfig_types.go
new file mode 100644
index 000000000..12b4d2974
--- /dev/null
+++ b/pkg/api/v1alpha1/kubeconfig_types.go
@@ -0,0 +1,29 @@
+/*
+ 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
+
+     https://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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api/v1"
+)
+
+// +kubebuilder:object:root=true
+
+// KubeConfig object for k8s client
+type KubeConfig struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+	Config            clientcmdapi.Config `json:"config,omitempty"`
+}
diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go
index aed7daeb1..20d9997a2 100644
--- a/pkg/api/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go
@@ -85,6 +85,32 @@ func (in *InitOptions) DeepCopy() *InitOptions {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *KubeConfig) DeepCopyInto(out *KubeConfig) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Config.DeepCopyInto(&out.Config)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfig.
+func (in *KubeConfig) DeepCopy() *KubeConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(KubeConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *KubeConfig) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MoveOptions) DeepCopyInto(out *MoveOptions) {
 	*out = *in
diff --git a/pkg/document/filesystem.go b/pkg/document/filesystem.go
index e059b7abe..eb51bd6a9 100644
--- a/pkg/document/filesystem.go
+++ b/pkg/document/filesystem.go
@@ -30,6 +30,7 @@ type File interface {
 type FileSystem interface {
 	fs.FileSystem
 	TempFile(string, string) (File, error)
+	TempDir(string, string) (string, error)
 }
 
 // Fs is adaptor to TempFile
@@ -46,3 +47,8 @@ func NewDocumentFs() FileSystem {
 func (dfs Fs) TempFile(tmpDir string, prefix string) (File, error) {
 	return ioutil.TempFile(tmpDir, prefix)
 }
+
+// TempDir creates a temporary directory in given root directory
+func (dfs Fs) TempDir(rootDir string, prefix string) (string, error) {
+	return ioutil.TempDir(rootDir, prefix)
+}
diff --git a/pkg/k8s/utils/utils.go b/pkg/k8s/utils/utils.go
index 37ab539fd..d2737f7b5 100644
--- a/pkg/k8s/utils/utils.go
+++ b/pkg/k8s/utils/utils.go
@@ -25,6 +25,9 @@ import (
 	cmdutil "k8s.io/kubectl/pkg/cmd/util"
 	"sigs.k8s.io/cli-utils/pkg/manifestreader"
 
+	"sigs.k8s.io/yaml"
+
+	airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
 	"opendev.org/airship/airshipctl/pkg/document"
 )
 
@@ -96,3 +99,27 @@ func (mbr *ManifestBundleReader) Read() ([]*resource.Info, error) {
 	}
 	return mbr.StreamReader.Read()
 }
+
+// DumpKubeConfig to temporary directory
+func DumpKubeConfig(kconf *airshipv1.KubeConfig, root string, fs document.FileSystem) (string, error) {
+	data, err := yaml.Marshal(kconf.Config)
+	if err != nil {
+		return "", err
+	}
+
+	dir, err := fs.TempDir(root, "")
+	if err != nil {
+		return "", err
+	}
+
+	file, err := fs.TempFile(dir, "")
+	if err != nil {
+		return "", err
+	}
+
+	_, err = file.Write(data)
+	if err != nil {
+		return "", err
+	}
+	return file.Name(), nil
+}
diff --git a/pkg/k8s/utils/utils_test.go b/pkg/k8s/utils/utils_test.go
index b0fbf164a..49e5fde5e 100644
--- a/pkg/k8s/utils/utils_test.go
+++ b/pkg/k8s/utils/utils_test.go
@@ -15,6 +15,7 @@
 package utils
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"testing"
@@ -23,7 +24,12 @@ import (
 	"github.com/stretchr/testify/require"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api/v1"
+
+	airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
 	"opendev.org/airship/airshipctl/pkg/document"
+	"opendev.org/airship/airshipctl/testutil/fs"
 )
 
 func TestDefaultManifestFactory(t *testing.T) {
@@ -89,6 +95,87 @@ func TestManifestBundleReader(t *testing.T) {
 	}
 }
 
+func TestDumpKubeConfig(t *testing.T) {
+	errTmpDir := errors.New("TmpDir error")
+	errTmpFile := errors.New("TmpFile error")
+	errWriteFile := errors.New("WriteFile error")
+
+	sampleKubeConfig := &airshipv1.KubeConfig{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "airshipit.org/v1alpha1",
+			Kind:       "KubeConfig",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "somename",
+		},
+		Config: clientcmdapi.Config{
+			APIVersion: "v1",
+			Kind:       "Config",
+		},
+	}
+
+	testCases := []struct {
+		name        string
+		fs          document.FileSystem
+		expectedErr error
+	}{
+		{
+			name: "Error temporary dir",
+			fs: fs.MockFileSystem{
+				MockTempDir: func() (string, error) {
+					return "", errTmpDir
+				},
+			},
+			expectedErr: errTmpDir,
+		},
+		{
+			name: "Error temporary file",
+			fs: fs.MockFileSystem{
+				MockTempDir:  func() (string, error) { return "someDir", nil },
+				MockTempFile: func() (document.File, error) { return nil, errTmpFile },
+			},
+			expectedErr: errTmpFile,
+		},
+		{
+			name: "Error write file",
+			fs: fs.MockFileSystem{
+				MockTempDir: func() (string, error) { return "someDir", nil },
+				MockTempFile: func() (document.File, error) {
+					return fs.TestFile{
+						MockName:  func() string { return "filename" },
+						MockWrite: func() (int, error) { return 0, errWriteFile },
+						MockClose: func() error { return nil },
+					}, nil
+				},
+				MockRemoveAll: func() error { return nil },
+			},
+			expectedErr: errWriteFile,
+		},
+		{
+			name: "Dump without errors",
+			fs: fs.MockFileSystem{
+				MockTempDir: func() (string, error) { return "someDir", nil },
+				MockTempFile: func() (document.File, error) {
+					return fs.TestFile{
+						MockName:  func() string { return "filename" },
+						MockWrite: func() (int, error) { return 0, nil },
+						MockClose: func() error { return nil },
+					}, nil
+				},
+				MockRemoveAll: func() error { return nil },
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		tt := test
+		t.Run(tt.name, func(t *testing.T) {
+			_, err := DumpKubeConfig(sampleKubeConfig, "ttt", tt.fs)
+			assert.Equal(t, tt.expectedErr, err)
+		})
+	}
+}
+
 type fakeReaderWriter struct {
 	readErr  error
 	writeErr error