diff --git a/pkg/cluster/clustermap/map.go b/pkg/cluster/clustermap/map.go index 989b51a2c..46d1ffd9e 100644 --- a/pkg/cluster/clustermap/map.go +++ b/pkg/cluster/clustermap/map.go @@ -116,7 +116,7 @@ func (cm clusterMap) ClusterKubeconfigContext(clusterName string) (string, error kubeContext := cluster.KubeconfigContext // if kubeContext is still empty, set it to clusterName - if kubeContext == "" { + if cluster.KubeconfigContext == "" { kubeContext = clusterName } diff --git a/pkg/k8s/kubeconfig/builder.go b/pkg/k8s/kubeconfig/builder.go index da05c594f..7f66fe671 100644 --- a/pkg/k8s/kubeconfig/builder.go +++ b/pkg/k8s/kubeconfig/builder.go @@ -15,12 +15,18 @@ package kubeconfig import ( + "bytes" "path/filepath" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" + + "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/cluster/clustermap" + "opendev.org/airship/airshipctl/pkg/clusterctl/client" "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/fs" + "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/util" ) @@ -40,7 +46,9 @@ type Builder struct { clusterName string root string - clusterMap clustermap.ClusterMap + clusterMap clustermap.ClusterMap + clusterctlClient client.Interface + fs fs.FileSystem } // WithPath allows to set path to prexisting kubeconfig @@ -73,25 +81,35 @@ func (b *Builder) WithTempRoot(root string) *Builder { return b } +// WithClusterctClient this is used if u want to inject your own clusterctl +// mostly needed for tests +func (b *Builder) WithClusterctClient(c client.Interface) *Builder { + b.clusterctlClient = c + return b +} + +// WithFilesytem allows to set filesystem +func (b *Builder) WithFilesytem(fs fs.FileSystem) *Builder { + b.fs = fs + return b +} + // Build builds a kubeconfig interface to be used func (b *Builder) Build() Interface { switch { case b.path != "": - fSys := fs.NewDocumentFs() - return NewKubeConfig(FromFile(b.path, fSys), InjectFilePath(b.path, fSys), InjectTempRoot(b.root)) + return NewKubeConfig(FromFile(b.path, b.fs), InjectFilePath(b.path, b.fs), InjectTempRoot(b.root)) case b.fromParent(): - // TODO add method that would get kubeconfig from parent cluster and glue it together - // with parent kubeconfig if needed - return NewKubeConfig(func() ([]byte, error) { - return nil, errors.ErrNotImplemented{} - }) + // TODO consider adding various drivers to source kubeconfig from + // Also consider accumulating different kubeconfigs, and returning one single + // large file, so that every executor has access to all parent clusters. + return NewKubeConfig(b.buildClusterctlFromParent, InjectTempRoot(b.root), InjectFileSystem(b.fs)) case b.bundlePath != "": - return NewKubeConfig(FromBundle(b.bundlePath), InjectTempRoot(b.root)) + return NewKubeConfig(FromBundle(b.bundlePath), InjectTempRoot(b.root), InjectFileSystem(b.fs)) default: - fSys := fs.NewDocumentFs() // return default path to kubeconfig file in airship workdir path := filepath.Join(util.UserHomeDir(), config.AirshipConfigDir, KubeconfigDefaultFileName) - return NewKubeConfig(FromFile(path, fSys), InjectFilePath(path, fSys), InjectTempRoot(b.root)) + return NewKubeConfig(FromFile(path, b.fs), InjectFilePath(path, b.fs), InjectTempRoot(b.root)) } } @@ -102,3 +120,103 @@ func (b *Builder) fromParent() bool { } return b.clusterMap.DynamicKubeConfig(b.clusterName) } + +func (b *Builder) buildClusterctlFromParent() ([]byte, error) { + currentCluster := b.clusterName + log.Printf("current cluster name is '%s'", + currentCluster) + parentCluster, err := b.clusterMap.ParentCluster(currentCluster) + if err != nil { + return nil, err + } + + parentKubeconfig := b.WithClusterName(parentCluster).Build() + + f, cleanup, err := parentKubeconfig.GetFile() + if err != nil { + return nil, err + } + defer cleanup() + + parentCtx, err := b.clusterMap.ClusterKubeconfigContext(parentCluster) + if err != nil { + return nil, err + } + + clusterAPIRef, err := b.clusterMap.ClusterAPIRef(currentCluster) + if err != nil { + return nil, err + } + + if b.clusterctlClient == nil { + b.clusterctlClient, err = client.NewClient("", log.DebugEnabled(), v1alpha1.DefaultClusterctl()) + if err != nil { + return nil, err + } + } + + log.Printf("Getting child kubeconfig from parent, parent context '%s', parent kubeconfing '%s'", + parentCtx, f) + + stringChild, err := b.clusterctlClient.GetKubeconfig(&client.GetKubeconfigOptions{ + ParentKubeconfigPath: f, + ParentKubeconfigContext: parentCtx, + ManagedClusterNamespace: clusterAPIRef.Namespace, + ManagedClusterName: clusterAPIRef.Name, + }) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer([]byte{}) + + err = parentKubeconfig.Write(buf) + if err != nil { + return nil, err + } + + parentObj, err := clientcmd.Load(buf.Bytes()) + if err != nil { + return nil, err + } + + childObj, err := clientcmd.Load([]byte(stringChild)) + if err != nil { + return nil, err + } + + childCtx, err := b.clusterMap.ClusterKubeconfigContext(currentCluster) + if err != nil { + return nil, err + } + log.Printf("Merging '%s' cluster kubeconfig into '%s' cluster kubeconfig", + currentCluster, parentCluster) + + return b.mergeOneContext(childCtx, parentObj, childObj) +} + +// merges two kubeconfigs, +func (b *Builder) mergeOneContext(contextOverride string, dst, src *api.Config) ([]byte, error) { + for key, content := range src.AuthInfos { + dst.AuthInfos[key] = content + } + + for key, content := range src.Clusters { + dst.Clusters[key] = content + } + + if len(src.Contexts) != 1 { + return nil, &ErrClusterctlKubeconfigWrongContextsCount{ + ContextCount: len(src.Contexts), + } + } + + for key, content := range src.Contexts { + if contextOverride == "" { + contextOverride = key + } + dst.Contexts[contextOverride] = content + } + + return clientcmd.Write(*dst) +} diff --git a/pkg/k8s/kubeconfig/builder_test.go b/pkg/k8s/kubeconfig/builder_test.go index f95864da1..eadd8416a 100644 --- a/pkg/k8s/kubeconfig/builder_test.go +++ b/pkg/k8s/kubeconfig/builder_test.go @@ -16,17 +16,65 @@ package kubeconfig_test import ( "bytes" + "fmt" + "io/ioutil" "path/filepath" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/cluster/clustermap" + "opendev.org/airship/airshipctl/pkg/clusterctl/client" "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/fs" "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/util" + "opendev.org/airship/airshipctl/testutil/clusterctl" + testfs "opendev.org/airship/airshipctl/testutil/fs" +) + +const ( + testKubeconfigString = `apiVersion: v1 +kind: Config +clusters: +- cluster: + certificate-authority-data: c29tZWNlcnQK + server: https://10.23.25.101:6443 + name: child_cluster +contexts: +- context: + cluster: child_cluster + user: child_user + name: child +current-context: dummy_cluster +preferences: {} +users: +- name: child_user + user: + client-certificate-data: c29tZWNlcnQK + client-key-data: c29tZWNlcnQK` + testKubeconfigStringSecond = `apiVersion: v1 +kind: Config +clusters: +- cluster: + certificate-authority-data: c29tZWNlcnQK + server: https://10.23.25.101:6443 + name: parent_cluster +contexts: +- context: + cluster: parent_cluster + user: parent_admin + name: parent-context +current-context: dummy_cluster +preferences: {} +users: +- name: parent_admin + user: + client-certificate-data: c29tZWNlcnQK + client-key-data: c29tZWNlcnQK` ) func TestBuilder(t *testing.T) { @@ -38,7 +86,7 @@ func TestBuilder(t *testing.T) { err := kube.Write(buf) require.NoError(t, err) // check that kubeconfig contains expected cluster string - assert.Contains(t, buf.String(), "dummycluster_ephemeral") + assert.Contains(t, buf.String(), "parent_parent_context") }) t.Run("Only filepath", func(t *testing.T) { @@ -52,33 +100,6 @@ func TestBuilder(t *testing.T) { assert.Contains(t, buf.String(), "dummycluster_ephemeral") }) - t.Run("Only cluster map", func(t *testing.T) { - childCluster := "child" - parentCluster := "parent" - clusterMap := &v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - childCluster: { - Parent: parentCluster, - DynamicKubeConfig: true, - }, - parentCluster: { - DynamicKubeConfig: false, - }, - }, - } - builder := kubeconfig.NewBuilder(). - WithClusterMap(clustermap.NewClusterMap(clusterMap)). - WithClusterName(childCluster) - kube := builder.Build() - // This should not be implemented yet, and we need to check that we are getting there - require.NotNil(t, kube) - filePath, cleanup, err := kube.GetFile() - require.Error(t, err) - require.Contains(t, err.Error(), "not implemented") - assert.Equal(t, "", filePath) - require.Nil(t, cleanup) - }) - t.Run("No current cluster, fall to default", func(t *testing.T) { clusterMap := &v1alpha1.ClusterMap{} builder := kubeconfig.NewBuilder(). @@ -93,27 +114,6 @@ func TestBuilder(t *testing.T) { assert.Equal(t, path, actualPath) }) - t.Run("Dynamic, but no parent", func(t *testing.T) { - childCluster := "child" - clusterMap := &v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - childCluster: { - DynamicKubeConfig: true, - }, - }, - } - builder := kubeconfig.NewBuilder(). - WithClusterMap(clustermap.NewClusterMap(clusterMap)). - WithClusterName(childCluster) - kube := builder.Build() - // We should get a default value for cluster, as we can't find parent cluster - filePath, cleanup, err := kube.GetFile() - require.Error(t, err) - require.Contains(t, err.Error(), "not implemented") - assert.Equal(t, "", filePath) - require.Nil(t, cleanup) - }) - t.Run("Default source", func(t *testing.T) { builder := kubeconfig.NewBuilder() kube := builder.Build() @@ -126,3 +126,158 @@ func TestBuilder(t *testing.T) { assert.Equal(t, path, actualPath) }) } + +func TestBuilderClusterctl(t *testing.T) { + childCluster := "child" + parentCluster := "parent" + testBundlePath := "testdata" + kubeconfigPath := filepath.Join(testBundlePath, "kubeconfig-12341234") + + tests := []struct { + name string + errString string + requestedClusterName string + parentClusterName string + tempRoot string + clusterMap clustermap.ClusterMap + clusterctlClient client.Interface + fs fs.FileSystem + }{ + { + name: "error no parent context", + errString: fmt.Sprintf("context \"%s\" does not exist", parentCluster), + parentClusterName: parentCluster, + requestedClusterName: childCluster, + clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ + Map: map[string]*v1alpha1.Cluster{ + childCluster: { + Parent: parentCluster, + DynamicKubeConfig: true, + }, + parentCluster: { + DynamicKubeConfig: false, + }, + }, + }), + }, + { + name: "error dynamic but no parrent", + parentClusterName: parentCluster, + requestedClusterName: childCluster, + errString: "failed to find a parent", + clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ + Map: map[string]*v1alpha1.Cluster{ + childCluster: { + DynamicKubeConfig: true, + }, + }, + }), + }, + { + name: "error write temp parent", + parentClusterName: parentCluster, + requestedClusterName: childCluster, + tempRoot: "does not exist anywhere", + errString: "no such file or directory", + clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ + Map: map[string]*v1alpha1.Cluster{ + childCluster: { + Parent: parentCluster, + DynamicKubeConfig: true, + }, + parentCluster: { + DynamicKubeConfig: false, + KubeconfigContext: "dummy_cluster", + }, + }, + }), + }, + { + name: "success", + parentClusterName: parentCluster, + requestedClusterName: childCluster, + clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ + Map: map[string]*v1alpha1.Cluster{ + childCluster: { + Parent: parentCluster, + DynamicKubeConfig: true, + }, + parentCluster: { + DynamicKubeConfig: true, + KubeconfigContext: "parent-custom", + Parent: "parent-parent", + }, + "parent-parent": { + DynamicKubeConfig: false, + KubeconfigContext: "parent_parent_context", + }, + }, + }), + tempRoot: "testdata", + fs: testfs.MockFileSystem{ + MockRemoveAll: func() error { return nil }, + MockTempFile: func(s1, s2 string) (fs.File, error) { + return testfs.TestFile{ + MockName: func() string { return kubeconfigPath }, + MockWrite: func() (int, error) { return 0, nil }, + MockClose: func() error { return nil }, + }, nil + }, + }, + clusterctlClient: func() client.Interface { + c := &clusterctl.MockInterface{ + Mock: mock.Mock{}, + } + c.On("GetKubeconfig", &client.GetKubeconfigOptions{ + ParentKubeconfigPath: kubeconfigPath, + ParentKubeconfigContext: "parent-custom", + ManagedClusterNamespace: clustermap.DefaultClusterAPIObjNamespace, + ManagedClusterName: childCluster, + }).Once().Return(testKubeconfigString, nil) + c.On("GetKubeconfig", &client.GetKubeconfigOptions{ + ParentKubeconfigPath: kubeconfigPath, + ParentKubeconfigContext: "parent_parent_context", + ManagedClusterNamespace: clustermap.DefaultClusterAPIObjNamespace, + ManagedClusterName: parentCluster, + }).Once().Return(testKubeconfigStringSecond, nil) + return c + }(), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + kube := kubeconfig.NewBuilder(). + WithClusterMap(tt.clusterMap). + WithClusterName(tt.requestedClusterName). + WithBundle(testBundlePath). + WithTempRoot(tt.tempRoot). + WithClusterctClient(tt.clusterctlClient). + WithFilesytem(tt.fs). + Build() + require.NotNil(t, kube) + filePath, cleanup, err := kube.GetFile() + // This is needed to avoid leftovers on test failures + if cleanup != nil { + defer cleanup() + } + + if tt.errString != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errString) + assert.Equal(t, "", filePath) + } else { + assert.NoError(t, err) + assert.NotEqual(t, "", filePath) + assert.NotNil(t, cleanup) + buf := bytes.NewBuffer([]byte{}) + err := kube.Write(buf) + require.NoError(t, err) + result, err := ioutil.ReadFile("testdata/result-kubeconf") + require.NoError(t, err) + assert.Equal(t, result, buf.Bytes()) + } + }) + } +} diff --git a/pkg/k8s/kubeconfig/errors.go b/pkg/k8s/kubeconfig/errors.go index 8f65b9576..78d5a8e0d 100644 --- a/pkg/k8s/kubeconfig/errors.go +++ b/pkg/k8s/kubeconfig/errors.go @@ -14,6 +14,8 @@ package kubeconfig +import "fmt" + // ErrKubeConfigPathEmpty returned when kubeconfig path is not specified type ErrKubeConfigPathEmpty struct { } @@ -21,3 +23,14 @@ type ErrKubeConfigPathEmpty struct { func (e *ErrKubeConfigPathEmpty) Error() string { return "kubeconfig path is not defined" } + +// ErrClusterctlKubeconfigWrongContextsCount is returned when clusterctl client returns +// multiple or no contexts in kubeconfig for the child cluster +type ErrClusterctlKubeconfigWrongContextsCount struct { + ContextCount int +} + +func (e *ErrClusterctlKubeconfigWrongContextsCount) Error() string { + return fmt.Sprintf("clusterctl client returned '%d' contexts in kubeconfig "+ + "context count must exactly one", e.ContextCount) +} diff --git a/pkg/k8s/kubeconfig/kubeconfig.go b/pkg/k8s/kubeconfig/kubeconfig.go index e0be4d159..57f1d19e0 100644 --- a/pkg/k8s/kubeconfig/kubeconfig.go +++ b/pkg/k8s/kubeconfig/kubeconfig.go @@ -26,6 +26,11 @@ import ( "opendev.org/airship/airshipctl/pkg/fs" ) +const ( + // KubeconfigPrefix is a prefix that is added when writing temporary kubeconfig files + KubeconfigPrefix = "kubeconfig-" +) + // Interface provides a uniform way to interact with kubeconfig file type Interface interface { // GetFile returns path to kubeconfig file and a function to remove it @@ -43,8 +48,9 @@ type Interface interface { var _ Interface = &kubeConfig{} type kubeConfig struct { - path string - dumpRoot string + path string + dumpRoot string + savedByes []byte fileSystem fs.FileSystem sourceFunc KubeSourceFunc @@ -104,6 +110,9 @@ func FromSecret(c client.Interface, o *client.GetKubeconfigOptions) KubeSourceFu // FromFile returns KubeSource type, uses path to kubeconfig on FS as source to construct kubeconfig object func FromFile(path string, fSys fs.FileSystem) KubeSourceFunc { return func() ([]byte, error) { + if fSys == nil { + fSys = fs.NewDocumentFs() + } return fSys.ReadFile(path) } } @@ -160,7 +169,7 @@ func InjectFilePath(path string, fSys fs.FileSystem) Option { } func (k *kubeConfig) WriteFile(path string) (err error) { - data, err := k.sourceFunc() + data, err := k.bytes() if err != nil { return err } @@ -168,7 +177,7 @@ func (k *kubeConfig) WriteFile(path string) (err error) { } func (k *kubeConfig) Write(w io.Writer) (err error) { - data, err := k.sourceFunc() + data, err := k.bytes() if err != nil { return err } @@ -178,11 +187,11 @@ func (k *kubeConfig) Write(w io.Writer) (err error) { // WriteTempFile implements kubeconfig Interface func (k *kubeConfig) WriteTempFile(root string) (string, Cleanup, error) { - data, err := k.sourceFunc() + data, err := k.bytes() if err != nil { return "", nil, err } - file, err := k.fileSystem.TempFile(root, "kubeconfig-") + file, err := k.fileSystem.TempFile(root, KubeconfigPrefix) if err != nil { return "", nil, err } @@ -197,6 +206,14 @@ func (k *kubeConfig) WriteTempFile(root string) (string, Cleanup, error) { return fName, cleanup(fName, k.fileSystem), nil } +func (k *kubeConfig) bytes() ([]byte, error) { + var err error + if len(k.savedByes) == 0 { + k.savedByes, err = k.sourceFunc() + } + return k.savedByes, err +} + // GetFile checks if path to kubeconfig is already set and returns it no cleanup is necessary, // and Cleanup() method will do nothing. // If path is not set kubeconfig will be written to temporary file system, returned path will diff --git a/pkg/k8s/kubeconfig/kubeconfig_test.go b/pkg/k8s/kubeconfig/kubeconfig_test.go index f7dd85c11..705257e96 100644 --- a/pkg/k8s/kubeconfig/kubeconfig_test.go +++ b/pkg/k8s/kubeconfig/kubeconfig_test.go @@ -54,27 +54,6 @@ users: user: client-certificate-data: cert-data client-key-data: client-keydata -` - //nolint: lll - testFullValidKubeconfig = `apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXlOakE0TWpneU5Gb1hEVEk1TVRJeU16QTRNamd5TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTTFSClM0d3lnajNpU0JBZjlCR0JUS1p5VTFwYmdDaGQ2WTdJektaZWRoakM2K3k1ZEJpWm81ZUx6Z2tEc2gzOC9YQ1MKenFPS2V5cE5RcDN5QVlLdmJKSHg3ODZxSFZZNjg1ZDVYVDNaOHNyVVRzVDR5WmNzZHAzV3lHdDM0eXYzNi9BSQoxK1NlUFErdU5JemN6bzNEdWhXR0ZoQjk3VjZwRitFUTBlVWN5bk05c2hkL3AwWVFzWDR1ZlhxaENENVpzZnZUCnBka3UvTWkyWnVGUldUUUtNeGpqczV3Z2RBWnBsNnN0L2ZkbmZwd1Q5cC9WTjRuaXJnMEsxOURTSFFJTHVrU2MKb013bXNBeDJrZmxITWhPazg5S3FpMEloL2cyczRFYTRvWURZemt0Y2JRZ24wd0lqZ2dmdnVzM3pRbEczN2lwYQo4cVRzS2VmVGdkUjhnZkJDNUZNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJek9BL00xWmRGUElzd2VoWjFuemJ0VFNURG4KRHMyVnhSV0VnclFFYzNSYmV3a1NkbTlBS3MwVGR0ZHdEbnBEL2tRYkNyS2xEeFF3RWg3NFZNSFZYYkFadDdsVwpCSm90T21xdXgxYThKYklDRTljR0FHRzFvS0g5R29jWERZY0JzOTA3ckxIdStpVzFnL0xVdG5hN1dSampqZnBLCnFGelFmOGdJUHZIM09BZ3B1RVVncUx5QU8ya0VnelZwTjZwQVJxSnZVRks2TUQ0YzFmMnlxWGxwNXhrN2dFSnIKUzQ4WmF6d0RmWUVmV3Jrdld1YWdvZ1M2SktvbjVEZ0Z1ZHhINXM2Snl6R3lPVnZ0eG1TY2FvOHNxaCs3UXkybgoyLzFVcU5ZK0hlN0x4d04rYkhwYkIxNUtIMTU5ZHNuS3BRbjRORG1jSTZrVnJ3MDVJMUg5ZGRBbGF0bz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - server: https://10.23.25.101:6443 - name: dummycluster_ephemeral -contexts: -- context: - cluster: dummycluster_ephemeral - user: kubernetes-admin - name: dummy_cluster -current-context: dummy_cluster -kind: Config -preferences: {} -users: -- name: kubernetes-admin - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= ` ) @@ -179,28 +158,26 @@ func TestFromSecret(t *testing.T) { func TestFromBundle(t *testing.T) { tests := []struct { - name string - rootPath string - shouldFail bool - expectedData []byte + name string + rootPath string + expectedContains string + shouldFail bool }{ { - name: "valid kubeconfig", - rootPath: "testdata", - shouldFail: false, - expectedData: []byte(testFullValidKubeconfig), + name: "valid kubeconfig", + rootPath: "testdata", + shouldFail: false, + expectedContains: "parent_parent_context", }, { - name: "wrong path", - rootPath: "wrong/path", - shouldFail: true, - expectedData: nil, + name: "wrong path", + rootPath: "wrong/path", + shouldFail: true, }, { - name: "kubeconfig not found", - rootPath: "testdata_fail", - shouldFail: true, - expectedData: nil, + name: "kubeconfig not found", + rootPath: "testdata_fail", + shouldFail: true, }, } @@ -213,7 +190,7 @@ func TestFromBundle(t *testing.T) { assert.Nil(t, kubeconf) } else { require.NoError(t, err) - assert.Equal(t, tt.expectedData, kubeconf) + assert.Contains(t, string(kubeconf), tt.expectedContains) } }) } diff --git a/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml b/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml index aae922c51..437b0fcde 100644 --- a/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml +++ b/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml @@ -9,16 +9,16 @@ config: - cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXlOakE0TWpneU5Gb1hEVEk1TVRJeU16QTRNamd5TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTTFSClM0d3lnajNpU0JBZjlCR0JUS1p5VTFwYmdDaGQ2WTdJektaZWRoakM2K3k1ZEJpWm81ZUx6Z2tEc2gzOC9YQ1MKenFPS2V5cE5RcDN5QVlLdmJKSHg3ODZxSFZZNjg1ZDVYVDNaOHNyVVRzVDR5WmNzZHAzV3lHdDM0eXYzNi9BSQoxK1NlUFErdU5JemN6bzNEdWhXR0ZoQjk3VjZwRitFUTBlVWN5bk05c2hkL3AwWVFzWDR1ZlhxaENENVpzZnZUCnBka3UvTWkyWnVGUldUUUtNeGpqczV3Z2RBWnBsNnN0L2ZkbmZwd1Q5cC9WTjRuaXJnMEsxOURTSFFJTHVrU2MKb013bXNBeDJrZmxITWhPazg5S3FpMEloL2cyczRFYTRvWURZemt0Y2JRZ24wd0lqZ2dmdnVzM3pRbEczN2lwYQo4cVRzS2VmVGdkUjhnZkJDNUZNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJek9BL00xWmRGUElzd2VoWjFuemJ0VFNURG4KRHMyVnhSV0VnclFFYzNSYmV3a1NkbTlBS3MwVGR0ZHdEbnBEL2tRYkNyS2xEeFF3RWg3NFZNSFZYYkFadDdsVwpCSm90T21xdXgxYThKYklDRTljR0FHRzFvS0g5R29jWERZY0JzOTA3ckxIdStpVzFnL0xVdG5hN1dSampqZnBLCnFGelFmOGdJUHZIM09BZ3B1RVVncUx5QU8ya0VnelZwTjZwQVJxSnZVRks2TUQ0YzFmMnlxWGxwNXhrN2dFSnIKUzQ4WmF6d0RmWUVmV3Jrdld1YWdvZ1M2SktvbjVEZ0Z1ZHhINXM2Snl6R3lPVnZ0eG1TY2FvOHNxaCs3UXkybgoyLzFVcU5ZK0hlN0x4d04rYkhwYkIxNUtIMTU5ZHNuS3BRbjRORG1jSTZrVnJ3MDVJMUg5ZGRBbGF0bz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= server: https://10.23.25.101:6443 - name: dummycluster_ephemeral + name: parent_parent_cluster contexts: - context: - cluster: dummycluster_ephemeral - user: kubernetes-admin - name: dummy_cluster + cluster: parent_parent_cluster + user: parent_parent_admin + name: parent_parent_context current-context: dummy_cluster preferences: {} users: - - name: kubernetes-admin + - name: parent_parent_admin user: client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/pkg/k8s/kubeconfig/testdata/result-kubeconf b/pkg/k8s/kubeconfig/testdata/result-kubeconf new file mode 100644 index 000000000..d3bcf7f2c --- /dev/null +++ b/pkg/k8s/kubeconfig/testdata/result-kubeconf @@ -0,0 +1,43 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: c29tZWNlcnQK + server: https://10.23.25.101:6443 + name: child_cluster +- cluster: + certificate-authority-data: c29tZWNlcnQK + server: https://10.23.25.101:6443 + name: parent_cluster +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXlOakE0TWpneU5Gb1hEVEk1TVRJeU16QTRNamd5TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTTFSClM0d3lnajNpU0JBZjlCR0JUS1p5VTFwYmdDaGQ2WTdJektaZWRoakM2K3k1ZEJpWm81ZUx6Z2tEc2gzOC9YQ1MKenFPS2V5cE5RcDN5QVlLdmJKSHg3ODZxSFZZNjg1ZDVYVDNaOHNyVVRzVDR5WmNzZHAzV3lHdDM0eXYzNi9BSQoxK1NlUFErdU5JemN6bzNEdWhXR0ZoQjk3VjZwRitFUTBlVWN5bk05c2hkL3AwWVFzWDR1ZlhxaENENVpzZnZUCnBka3UvTWkyWnVGUldUUUtNeGpqczV3Z2RBWnBsNnN0L2ZkbmZwd1Q5cC9WTjRuaXJnMEsxOURTSFFJTHVrU2MKb013bXNBeDJrZmxITWhPazg5S3FpMEloL2cyczRFYTRvWURZemt0Y2JRZ24wd0lqZ2dmdnVzM3pRbEczN2lwYQo4cVRzS2VmVGdkUjhnZkJDNUZNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJek9BL00xWmRGUElzd2VoWjFuemJ0VFNURG4KRHMyVnhSV0VnclFFYzNSYmV3a1NkbTlBS3MwVGR0ZHdEbnBEL2tRYkNyS2xEeFF3RWg3NFZNSFZYYkFadDdsVwpCSm90T21xdXgxYThKYklDRTljR0FHRzFvS0g5R29jWERZY0JzOTA3ckxIdStpVzFnL0xVdG5hN1dSampqZnBLCnFGelFmOGdJUHZIM09BZ3B1RVVncUx5QU8ya0VnelZwTjZwQVJxSnZVRks2TUQ0YzFmMnlxWGxwNXhrN2dFSnIKUzQ4WmF6d0RmWUVmV3Jrdld1YWdvZ1M2SktvbjVEZ0Z1ZHhINXM2Snl6R3lPVnZ0eG1TY2FvOHNxaCs3UXkybgoyLzFVcU5ZK0hlN0x4d04rYkhwYkIxNUtIMTU5ZHNuS3BRbjRORG1jSTZrVnJ3MDVJMUg5ZGRBbGF0bz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + server: https://10.23.25.101:6443 + name: parent_parent_cluster +contexts: +- context: + cluster: child_cluster + user: child_user + name: child +- context: + cluster: parent_cluster + user: parent_admin + name: parent-custom +- context: + cluster: parent_parent_cluster + user: parent_parent_admin + name: parent_parent_context +current-context: dummy_cluster +kind: Config +preferences: {} +users: +- name: child_user + user: + client-certificate-data: c29tZWNlcnQK + client-key-data: c29tZWNlcnQK +- name: parent_admin + user: + client-certificate-data: c29tZWNlcnQK + client-key-data: c29tZWNlcnQK +- name: parent_parent_admin + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/testutil/clusterctl/client.go b/testutil/clusterctl/client.go new file mode 100644 index 000000000..6c47b768c --- /dev/null +++ b/testutil/clusterctl/client.go @@ -0,0 +1,68 @@ +/* + 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 clusterctl + +import ( + "fmt" + + "github.com/stretchr/testify/mock" + + "opendev.org/airship/airshipctl/pkg/clusterctl/client" +) + +var _ client.Interface = &MockInterface{} + +// MockInterface provides mock interface for clusterctl +type MockInterface struct { + mock.Mock +} + +// Init to be implemented +func (m *MockInterface) Init(kubeconfigPath, kubeconfigContext string) error { + return nil +} + +// Move to be implemented +func (m *MockInterface) Move(fkp, fkc, tkp, tkc, namespace string) error { + return nil +} + +// Render to be implemented +func (m *MockInterface) Render(client.RenderOptions) ([]byte, error) { + return nil, nil +} + +// GetKubeconfig allows to control exepected input to the function and check expected output +// example usage: +// c := &clusterctl.MockInterface{ +// Mock: mock.Mock{}, +// } +// c.On("GetKubeconfig").Once().Return(&client.GetKubeconfigOptions{ +// ParentKubeconfigPath: filepath.Join("testdata", kubeconfig.KubeconfigPrefix), +// ParentKubeconfigContext: "dummy_cluster", +// ManagedClusterNamespace: clustermap.DefaultClusterAPIObjNamespace, +// ManagedClusterName: childCluster, +// }, "kubeconfig data", nil) +// first argument in return function is what you expect as input +// second argument is resulting expected string +// third is resulting error +func (m *MockInterface) GetKubeconfig(options *client.GetKubeconfigOptions) (string, error) { + args := m.Called(options) + expectedResult, ok := args.Get(0).(string) + if !ok { + return "", fmt.Errorf("wrong input") + } + return expectedResult, args.Error(1) +}