From 1f932861d47d52850f66fd373e1abecf20837d3e Mon Sep 17 00:00:00 2001 From: Kostiantyn Kalynovskyi Date: Mon, 24 Aug 2020 23:21:08 -0500 Subject: [PATCH] Add kubeconfig builder Kubeconfig builder will be a single source of kubeconfig, allowing to make a decision how to build kubeconfig based on different parameters, such as path to kubeconfig, dynamic kubeconfig options, or kubeconfig based on document bundle. Change-Id: Ia63e11a6f0b327e283d3e7fce169a35d54684dfb --- pkg/api/v1alpha1/cluster_map_types.go | 38 ++++++ pkg/api/v1alpha1/groupversion_info.go | 1 + pkg/api/v1alpha1/phase_types.go | 4 - pkg/api/v1alpha1/zz_generated.deepcopy.go | 55 ++++++++ pkg/clusterctl/client/executor.go | 21 +-- pkg/clusterctl/client/executor_test.go | 7 +- pkg/k8s/kubeconfig/builder.go | 116 ++++++++++++++++ pkg/k8s/kubeconfig/builder_test.go | 127 ++++++++++++++++++ pkg/k8s/kubeconfig/testdata/kubeconfig | 19 +++ pkg/phase/ifc/executor.go | 2 + pkg/phase/phase.go | 79 +++++++---- .../valid_site/phases/cluster_map.yaml | 9 ++ .../valid_site/phases/kustomization.yaml | 1 + 13 files changed, 437 insertions(+), 42 deletions(-) create mode 100644 pkg/api/v1alpha1/cluster_map_types.go create mode 100644 pkg/k8s/kubeconfig/builder.go create mode 100644 pkg/k8s/kubeconfig/builder_test.go create mode 100644 pkg/k8s/kubeconfig/testdata/kubeconfig create mode 100644 pkg/phase/testdata/valid_site/phases/cluster_map.yaml diff --git a/pkg/api/v1alpha1/cluster_map_types.go b/pkg/api/v1alpha1/cluster_map_types.go new file mode 100644 index 000000000..e650a5f5e --- /dev/null +++ b/pkg/api/v1alpha1/cluster_map_types.go @@ -0,0 +1,38 @@ +/* + 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" +) + +// +kubebuilder:object:root=true + +// ClusterMap represents cluster defined for this manifest +type ClusterMap struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + // Keys in this map MUST correspond to context names in kubeconfigs provided + Map map[string]*Cluster +} + +// Cluster uniquely identifies a cluster and its parent cluster +type Cluster struct { + // Parent is a key in ClusterMap.Map that identifies the name of the parent(management) cluster + Parent string `json:"parent,omitempty"` + // DynamicKubeConfig kubeconfig allows to get kubeconfig from parent cluster, instead + // expecting it to be in document bundle. Parent kubeconfig will be used to get kubeconfig + DynamicKubeConfig bool `json:"dynamicKubeConf,omitempty"` +} diff --git a/pkg/api/v1alpha1/groupversion_info.go b/pkg/api/v1alpha1/groupversion_info.go index d4dd6f846..fe14e81ef 100644 --- a/pkg/api/v1alpha1/groupversion_info.go +++ b/pkg/api/v1alpha1/groupversion_info.go @@ -48,6 +48,7 @@ func init() { &KubernetesApply{}, &ImageConfiguration{}, &RemoteDirectConfiguration{}, + &ClusterMap{}, ) _ = AddToScheme(Scheme) //nolint:errcheck } diff --git a/pkg/api/v1alpha1/phase_types.go b/pkg/api/v1alpha1/phase_types.go index 0a7d8e900..c3f285d15 100644 --- a/pkg/api/v1alpha1/phase_types.go +++ b/pkg/api/v1alpha1/phase_types.go @@ -33,8 +33,4 @@ type Phase struct { type PhaseConfig struct { ExecutorRef *corev1.ObjectReference `json:"executorRef"` DocumentEntryPoint string `json:"documentEntryPoint"` - // Name used to identify a cluster that the phase belongs to - ClusterName string `json:"clusterName"` - // ClusterNamespace identifies the namespace that the phase belongs to - ClusterNamespace string `json:"clusterNamespace"` } diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 58b5dc472..9933f4ac1 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -56,6 +56,61 @@ func (in *ApplyWaitOptions) DeepCopy() *ApplyWaitOptions { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Cluster) DeepCopyInto(out *Cluster) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. +func (in *Cluster) DeepCopy() *Cluster { + if in == nil { + return nil + } + out := new(Cluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterMap) DeepCopyInto(out *ClusterMap) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Map != nil { + in, out := &in.Map, &out.Map + *out = make(map[string]*Cluster, len(*in)) + for key, val := range *in { + var outVal *Cluster + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(Cluster) + **out = **in + } + (*out)[key] = outVal + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterMap. +func (in *ClusterMap) DeepCopy() *ClusterMap { + if in == nil { + return nil + } + out := new(ClusterMap) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterMap) 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 *Clusterctl) DeepCopyInto(out *Clusterctl) { *out = *in diff --git a/pkg/clusterctl/client/executor.go b/pkg/clusterctl/client/executor.go index d50159e47..a2dbb0812 100644 --- a/pkg/clusterctl/client/executor.go +++ b/pkg/clusterctl/client/executor.go @@ -33,11 +33,13 @@ var _ ifc.Executor = &ClusterctlExecutor{} // ClusterctlExecutor phase executor type ClusterctlExecutor struct { + clusterName string + dumpRoot string + Interface - bundle document.Bundle - options *airshipv1.Clusterctl - kubecfg kubeconfig.Interface - dumpRoot string + bundle document.Bundle + options *airshipv1.Clusterctl + kubecfg kubeconfig.Interface } // RegisterExecutor adds executor to phase executor registry @@ -66,11 +68,12 @@ func NewExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) { return nil, err } return &ClusterctlExecutor{ - Interface: client, - bundle: cfg.ExecutorBundle, - options: options, - kubecfg: cfg.KubeConfig, - dumpRoot: filepath.Dir(cfg.AirshipSettings.AirshipConfigPath), + clusterName: cfg.ClusterName, + Interface: client, + bundle: cfg.ExecutorBundle, + options: options, + kubecfg: cfg.KubeConfig, + dumpRoot: filepath.Dir(cfg.AirshipSettings.AirshipConfigPath), }, nil } diff --git a/pkg/clusterctl/client/executor_test.go b/pkg/clusterctl/client/executor_test.go index 064754836..6f6007b41 100644 --- a/pkg/clusterctl/client/executor_test.go +++ b/pkg/clusterctl/client/executor_test.go @@ -171,12 +171,7 @@ func TestExecutorRun(t *testing.T) { }, }, }, - { - name: "Regular Run move", - cfgDoc: executorDoc(t, "move"), - bundlePath: "testdata/executor_move", - expectedEvt: []events.Event{wrapError(airerrors.ErrNotImplemented{})}, - }, + // TODO add move tests here } for _, test := range testCases { tt := test diff --git a/pkg/k8s/kubeconfig/builder.go b/pkg/k8s/kubeconfig/builder.go new file mode 100644 index 000000000..54e172420 --- /dev/null +++ b/pkg/k8s/kubeconfig/builder.go @@ -0,0 +1,116 @@ +/* + 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 kubeconfig + +import ( + "path/filepath" + + "opendev.org/airship/airshipctl/pkg/api/v1alpha1" + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/document" + "opendev.org/airship/airshipctl/pkg/errors" + "opendev.org/airship/airshipctl/pkg/log" + "opendev.org/airship/airshipctl/pkg/util" +) + +// KubeconfigDefaultFileName is a default name for kubeconfig +const KubeconfigDefaultFileName = "kubeconfig" + +// NewBuilder returns instance of kubeconfig builder. +func NewBuilder() *Builder { + return &Builder{} +} + +// Builder is an object that allows to build a kubeconfig based on various provided sources +// such as path to kubeconfig, path to bundle that should contain kubeconfig and parent cluster +type Builder struct { + path string + bundlePath string + clusterName string + root string + + clusterMap *v1alpha1.ClusterMap +} + +// WithPath allows to set path to prexisting kubeconfig +func (b *Builder) WithPath(filePath string) *Builder { + b.path = filePath + return b +} + +// WithBundle allows to set path to bundle that should contain kubeconfig api object +func (b *Builder) WithBundle(bundlePath string) *Builder { + b.bundlePath = bundlePath + return b +} + +// WithClusterMap allows to set a parent cluster, that can be used to extract kubeconfig for target cluster +func (b *Builder) WithClusterMap(cMap *v1alpha1.ClusterMap) *Builder { + b.clusterMap = cMap + return b +} + +// WithClusterName allows to reach to a cluster to download kubeconfig from there +func (b *Builder) WithClusterName(clusterName string) *Builder { + b.clusterName = clusterName + return b +} + +// WithTempRoot allows to set temp root for kubeconfig +func (b *Builder) WithTempRoot(root string) *Builder { + b.root = root + return b +} + +// Build builds a kubeconfig interface to be used +func (b *Builder) Build() Interface { + switch { + case b.path != "": + fs := document.NewDocumentFs() + return NewKubeConfig(FromFile(b.path, fs), InjectFilePath(b.path, 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{} + }) + case b.bundlePath != "": + return NewKubeConfig(FromBundle(b.bundlePath), InjectTempRoot(b.root)) + default: + fs := document.NewDocumentFs() + // return default path to kubeconfig file in airship workdir + path := filepath.Join(util.UserHomeDir(), config.AirshipConfigDir, KubeconfigDefaultFileName) + return NewKubeConfig(FromFile(path, fs), InjectFilePath(path, fs), InjectTempRoot(b.root)) + } +} + +// fromParent checks if we should get kubeconfig from parent cluster secret +func (b *Builder) fromParent() bool { + if b.clusterMap == nil { + return false + } + currentCluster, exists := b.clusterMap.Map[b.clusterName] + if !exists { + log.Debugf("cluster %s is not defined in cluster map %v", b.clusterName, b.clusterMap) + return false + } + // Check if DynamicKubeConfig is enabled, if so that means, we should get kubeconfig + // for this cluster from its parent + if currentCluster.Parent == "" || !currentCluster.DynamicKubeConfig { + log.Debugf("dynamic kubeconfig or parent cluster is not set for cluster %s", b.clusterName) + return false + } + return true +} diff --git a/pkg/k8s/kubeconfig/builder_test.go b/pkg/k8s/kubeconfig/builder_test.go new file mode 100644 index 000000000..6dcba6511 --- /dev/null +++ b/pkg/k8s/kubeconfig/builder_test.go @@ -0,0 +1,127 @@ +/* + 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 kubeconfig_test + +import ( + "bytes" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "opendev.org/airship/airshipctl/pkg/api/v1alpha1" + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" + "opendev.org/airship/airshipctl/pkg/util" +) + +func TestBuilder(t *testing.T) { + t.Run("Only bundle", func(t *testing.T) { + builder := kubeconfig.NewBuilder().WithBundle("testdata") + kube := builder.Build() + require.NotNil(t, kube) + buf := bytes.NewBuffer([]byte{}) + err := kube.Write(buf) + require.NoError(t, err) + // check that kubeconfig contains expected cluster string + assert.Contains(t, buf.String(), "dummycluster_ephemeral") + }) + + t.Run("Only filepath", func(t *testing.T) { + builder := kubeconfig.NewBuilder().WithPath("testdata/kubeconfig") + kube := builder.Build() + require.NotNil(t, kube) + buf := bytes.NewBuffer([]byte{}) + err := kube.Write(buf) + require.NoError(t, err) + // check that kubeconfig contains expected cluster string + 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). + 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(). + WithClusterMap(clusterMap). + WithClusterName("some-cluster") + kube := builder.Build() + // We should get a default value for cluster since we don't have some-cluster set + actualPath, cleanup, err := kube.GetFile() + require.NoError(t, err) + defer cleanup() + path := filepath.Join(util.UserHomeDir(), config.AirshipConfigDir, kubeconfig.KubeconfigDefaultFileName) + assert.Equal(t, path, actualPath) + }) + + t.Run("No parent cluster is defined, fall to default", func(t *testing.T) { + childCluster := "child" + clusterMap := &v1alpha1.ClusterMap{ + Map: map[string]*v1alpha1.Cluster{ + childCluster: { + DynamicKubeConfig: true, + }, + }, + } + builder := kubeconfig.NewBuilder(). + WithClusterMap(clusterMap). + WithClusterName(childCluster) + kube := builder.Build() + // We should get a default value for cluster, as we can't find parent cluster + actualPath, cleanup, err := kube.GetFile() + defer cleanup() + require.NoError(t, err) + path := filepath.Join(util.UserHomeDir(), config.AirshipConfigDir, kubeconfig.KubeconfigDefaultFileName) + assert.Equal(t, path, actualPath) + }) + + t.Run("Default source", func(t *testing.T) { + builder := kubeconfig.NewBuilder() + kube := builder.Build() + // When ClusterMap is specified, but it doesn't have cluster-name defined, and no + // other sources provided, + actualPath, cleanup, err := kube.GetFile() + defer cleanup() + require.NoError(t, err) + path := filepath.Join(util.UserHomeDir(), config.AirshipConfigDir, kubeconfig.KubeconfigDefaultFileName) + assert.Equal(t, path, actualPath) + }) +} diff --git a/pkg/k8s/kubeconfig/testdata/kubeconfig b/pkg/k8s/kubeconfig/testdata/kubeconfig new file mode 100644 index 000000000..ea466e382 --- /dev/null +++ b/pkg/k8s/kubeconfig/testdata/kubeconfig @@ -0,0 +1,19 @@ + apiVersion: v1 + kind: Config + 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 + preferences: {} + users: + - name: kubernetes-admin + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/pkg/phase/ifc/executor.go b/pkg/phase/ifc/executor.go index 05aae242f..e69425ff6 100644 --- a/pkg/phase/ifc/executor.go +++ b/pkg/phase/ifc/executor.go @@ -18,6 +18,7 @@ import ( "io" "time" + "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/events" @@ -55,6 +56,7 @@ type ExecutorConfig struct { PhaseName string ClusterName string + ClusterMap *v1alpha1.ClusterMap ExecutorDocument document.Document ExecutorBundle document.Bundle AirshipSettings *environment.AirshipCTLSettings diff --git a/pkg/phase/phase.go b/pkg/phase/phase.go index e8d2ad740..4eb0a02ec 100644 --- a/pkg/phase/phase.go +++ b/pkg/phase/phase.go @@ -22,7 +22,6 @@ import ( airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" clusterctl "opendev.org/airship/airshipctl/pkg/clusterctl/client" - "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/events" @@ -31,7 +30,6 @@ import ( k8sutils "opendev.org/airship/airshipctl/pkg/k8s/utils" "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/phase/ifc" - "opendev.org/airship/airshipctl/pkg/util" ) // ExecutorRegistry returns map with executor factories @@ -107,6 +105,28 @@ func (p *Cmd) GetPhase(name string) (*airshipv1.Phase, error) { return phaseConfig, nil } +// GetClusterMap returns cluster map object +func (p *Cmd) GetClusterMap() (*airshipv1.ClusterMap, error) { + bundle, err := p.getBundle() + if err != nil { + return nil, err + } + clusterMap := &airshipv1.ClusterMap{} + selector, err := document.NewSelector().ByObject(clusterMap, airshipv1.Scheme) + if err != nil { + return nil, err + } + doc, err := bundle.SelectOne(selector) + if err != nil { + return nil, err + } + + if err = doc.ToAPIObject(clusterMap, airshipv1.Scheme); err != nil { + return nil, err + } + return clusterMap, nil +} + // GetExecutor referenced in a phase configuration func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) { bundle, err := p.getBundle() @@ -131,9 +151,14 @@ func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) { if err != nil { return nil, err } - executorDocBundle, err := document.NewBundleByPath(filepath.Join(targetPath, phaseConfig.DocumentEntryPoint)) - if err != nil { - return nil, err + var executorDocBundle document.Bundle + // if entrypoint is defined use it, if not, just pass nil bundle, executors should be ready for that + if phaseConfig.DocumentEntryPoint != "" { + bundlePath := filepath.Join(targetPath, phaseConfig.DocumentEntryPoint) + executorDocBundle, err = document.NewBundleByPath(bundlePath) + if err != nil { + return nil, err + } } if p.Registry == nil { p.Registry = DefaultExecutorRegistry @@ -143,25 +168,33 @@ func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) { if !found { return nil, ErrExecutorNotFound{GVK: refGVK} } + meta, err := p.Config.CurrentContextManifestMetadata() + if err != nil { + return nil, err + } + cMap, err := p.GetClusterMap() + if err != nil { + return nil, err + } - kubeConfPath := p.AirshipCTLSettings.Config.KubeConfigPath() - homeDir := util.UserHomeDir() - workDir := filepath.Join(homeDir, config.AirshipConfigDir) - fs := document.NewDocumentFs() - source := kubeconfig.FromFile(kubeConfPath, fs) - fileOption := kubeconfig.InjectFilePath(kubeConfPath, fs) - tempRootOption := kubeconfig.InjectTempRoot(workDir) - kubeConfig := kubeconfig.NewKubeConfig(source, fileOption, tempRootOption) - - // TODO add function to decide on how to build kubeconfig instead of hardcoding it here, - // when more kubeconfigs sources are available. - return executorFactory(ifc.ExecutorConfig{ - ExecutorBundle: executorDocBundle, - PhaseName: phase.Name, - ExecutorDocument: executorDoc, - AirshipSettings: p.AirshipCTLSettings, - KubeConfig: kubeConfig, - }) + kubeConfig := kubeconfig.NewBuilder(). + // TODO add kubeconfig flags path here, when kubeconfig flag is not controlled + // by config module during config loading. + WithBundle(meta.PhaseMeta.Path). + WithClusterMap(cMap). + WithClusterName(phase.ClusterName). + WithTempRoot(filepath.Dir(p.Config.LoadedConfigPath())). + Build() + return executorFactory( + ifc.ExecutorConfig{ + ExecutorBundle: executorDocBundle, + PhaseName: phase.Name, + ExecutorDocument: executorDoc, + AirshipSettings: p.AirshipCTLSettings, + KubeConfig: kubeConfig, + ClusterName: phase.ClusterName, + ClusterMap: cMap, + }) } // Exec starts executor goroutine and processes the events diff --git a/pkg/phase/testdata/valid_site/phases/cluster_map.yaml b/pkg/phase/testdata/valid_site/phases/cluster_map.yaml new file mode 100644 index 000000000..1a2318a81 --- /dev/null +++ b/pkg/phase/testdata/valid_site/phases/cluster_map.yaml @@ -0,0 +1,9 @@ +apiVersion: airshipit.org/v1alpha1 +kind: ClusterMap +metadata: + name: clusterctl-v1 +plan: + target: + parent: ephemeral + dynamicKubeConf: false + ephemeral: {} \ No newline at end of file diff --git a/pkg/phase/testdata/valid_site/phases/kustomization.yaml b/pkg/phase/testdata/valid_site/phases/kustomization.yaml index 799051b7a..fe91297a3 100644 --- a/pkg/phase/testdata/valid_site/phases/kustomization.yaml +++ b/pkg/phase/testdata/valid_site/phases/kustomization.yaml @@ -5,3 +5,4 @@ resources: - capi_init.yaml - clusterctl.yaml - kubernetes_apply.yaml + - cluster_map.yaml