Merge "Add kubeconfig builder"

This commit is contained in:
Zuul 2020-09-02 23:58:56 +00:00 committed by Gerrit Code Review
commit ead4f56f25
13 changed files with 437 additions and 42 deletions

View File

@ -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"`
}

View File

@ -48,6 +48,7 @@ func init() {
&KubernetesApply{},
&ImageConfiguration{},
&RemoteDirectConfiguration{},
&ClusterMap{},
)
_ = AddToScheme(Scheme) //nolint:errcheck
}

View File

@ -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"`
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
})
}

19
pkg/k8s/kubeconfig/testdata/kubeconfig vendored Normal file
View File

@ -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=

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
apiVersion: airshipit.org/v1alpha1
kind: ClusterMap
metadata:
name: clusterctl-v1
plan:
target:
parent: ephemeral
dynamicKubeConf: false
ephemeral: {}

View File

@ -5,3 +5,4 @@ resources:
- capi_init.yaml
- clusterctl.yaml
- kubernetes_apply.yaml
- cluster_map.yaml