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
This commit is contained in:
38
pkg/api/v1alpha1/cluster_map_types.go
Normal file
38
pkg/api/v1alpha1/cluster_map_types.go
Normal 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"`
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ func init() {
|
|||||||
&KubernetesApply{},
|
&KubernetesApply{},
|
||||||
&ImageConfiguration{},
|
&ImageConfiguration{},
|
||||||
&RemoteDirectConfiguration{},
|
&RemoteDirectConfiguration{},
|
||||||
|
&ClusterMap{},
|
||||||
)
|
)
|
||||||
_ = AddToScheme(Scheme) //nolint:errcheck
|
_ = AddToScheme(Scheme) //nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,4 @@ type Phase struct {
|
|||||||
type PhaseConfig struct {
|
type PhaseConfig struct {
|
||||||
ExecutorRef *corev1.ObjectReference `json:"executorRef"`
|
ExecutorRef *corev1.ObjectReference `json:"executorRef"`
|
||||||
DocumentEntryPoint string `json:"documentEntryPoint"`
|
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"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,61 @@ func (in *ApplyWaitOptions) DeepCopy() *ApplyWaitOptions {
|
|||||||
return out
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Clusterctl) DeepCopyInto(out *Clusterctl) {
|
func (in *Clusterctl) DeepCopyInto(out *Clusterctl) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|||||||
@@ -33,11 +33,13 @@ var _ ifc.Executor = &ClusterctlExecutor{}
|
|||||||
|
|
||||||
// ClusterctlExecutor phase executor
|
// ClusterctlExecutor phase executor
|
||||||
type ClusterctlExecutor struct {
|
type ClusterctlExecutor struct {
|
||||||
|
clusterName string
|
||||||
|
dumpRoot string
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
bundle document.Bundle
|
bundle document.Bundle
|
||||||
options *airshipv1.Clusterctl
|
options *airshipv1.Clusterctl
|
||||||
kubecfg kubeconfig.Interface
|
kubecfg kubeconfig.Interface
|
||||||
dumpRoot string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterExecutor adds executor to phase executor registry
|
// RegisterExecutor adds executor to phase executor registry
|
||||||
@@ -66,6 +68,7 @@ func NewExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ClusterctlExecutor{
|
return &ClusterctlExecutor{
|
||||||
|
clusterName: cfg.ClusterName,
|
||||||
Interface: client,
|
Interface: client,
|
||||||
bundle: cfg.ExecutorBundle,
|
bundle: cfg.ExecutorBundle,
|
||||||
options: options,
|
options: options,
|
||||||
|
|||||||
@@ -171,12 +171,7 @@ func TestExecutorRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// TODO add move tests here
|
||||||
name: "Regular Run move",
|
|
||||||
cfgDoc: executorDoc(t, "move"),
|
|
||||||
bundlePath: "testdata/executor_move",
|
|
||||||
expectedEvt: []events.Event{wrapError(airerrors.ErrNotImplemented{})},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
tt := test
|
tt := test
|
||||||
|
|||||||
116
pkg/k8s/kubeconfig/builder.go
Normal file
116
pkg/k8s/kubeconfig/builder.go
Normal 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
|
||||||
|
}
|
||||||
127
pkg/k8s/kubeconfig/builder_test.go
Normal file
127
pkg/k8s/kubeconfig/builder_test.go
Normal 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
19
pkg/k8s/kubeconfig/testdata/kubeconfig
vendored
Normal 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=
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||||
"opendev.org/airship/airshipctl/pkg/document"
|
"opendev.org/airship/airshipctl/pkg/document"
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/events"
|
"opendev.org/airship/airshipctl/pkg/events"
|
||||||
@@ -55,6 +56,7 @@ type ExecutorConfig struct {
|
|||||||
PhaseName string
|
PhaseName string
|
||||||
ClusterName string
|
ClusterName string
|
||||||
|
|
||||||
|
ClusterMap *v1alpha1.ClusterMap
|
||||||
ExecutorDocument document.Document
|
ExecutorDocument document.Document
|
||||||
ExecutorBundle document.Bundle
|
ExecutorBundle document.Bundle
|
||||||
AirshipSettings *environment.AirshipCTLSettings
|
AirshipSettings *environment.AirshipCTLSettings
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
|
|
||||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||||
clusterctl "opendev.org/airship/airshipctl/pkg/clusterctl/client"
|
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/document"
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/events"
|
"opendev.org/airship/airshipctl/pkg/events"
|
||||||
@@ -31,7 +30,6 @@ import (
|
|||||||
k8sutils "opendev.org/airship/airshipctl/pkg/k8s/utils"
|
k8sutils "opendev.org/airship/airshipctl/pkg/k8s/utils"
|
||||||
"opendev.org/airship/airshipctl/pkg/log"
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||||
"opendev.org/airship/airshipctl/pkg/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecutorRegistry returns map with executor factories
|
// ExecutorRegistry returns map with executor factories
|
||||||
@@ -107,6 +105,28 @@ func (p *Cmd) GetPhase(name string) (*airshipv1.Phase, error) {
|
|||||||
return phaseConfig, nil
|
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
|
// GetExecutor referenced in a phase configuration
|
||||||
func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) {
|
func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) {
|
||||||
bundle, err := p.getBundle()
|
bundle, err := p.getBundle()
|
||||||
@@ -131,10 +151,15 @@ func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
executorDocBundle, err := document.NewBundleByPath(filepath.Join(targetPath, phaseConfig.DocumentEntryPoint))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if p.Registry == nil {
|
if p.Registry == nil {
|
||||||
p.Registry = DefaultExecutorRegistry
|
p.Registry = DefaultExecutorRegistry
|
||||||
}
|
}
|
||||||
@@ -143,24 +168,32 @@ func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) {
|
|||||||
if !found {
|
if !found {
|
||||||
return nil, ErrExecutorNotFound{GVK: refGVK}
|
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()
|
kubeConfig := kubeconfig.NewBuilder().
|
||||||
homeDir := util.UserHomeDir()
|
// TODO add kubeconfig flags path here, when kubeconfig flag is not controlled
|
||||||
workDir := filepath.Join(homeDir, config.AirshipConfigDir)
|
// by config module during config loading.
|
||||||
fs := document.NewDocumentFs()
|
WithBundle(meta.PhaseMeta.Path).
|
||||||
source := kubeconfig.FromFile(kubeConfPath, fs)
|
WithClusterMap(cMap).
|
||||||
fileOption := kubeconfig.InjectFilePath(kubeConfPath, fs)
|
WithClusterName(phase.ClusterName).
|
||||||
tempRootOption := kubeconfig.InjectTempRoot(workDir)
|
WithTempRoot(filepath.Dir(p.Config.LoadedConfigPath())).
|
||||||
kubeConfig := kubeconfig.NewKubeConfig(source, fileOption, tempRootOption)
|
Build()
|
||||||
|
return executorFactory(
|
||||||
// TODO add function to decide on how to build kubeconfig instead of hardcoding it here,
|
ifc.ExecutorConfig{
|
||||||
// when more kubeconfigs sources are available.
|
|
||||||
return executorFactory(ifc.ExecutorConfig{
|
|
||||||
ExecutorBundle: executorDocBundle,
|
ExecutorBundle: executorDocBundle,
|
||||||
PhaseName: phase.Name,
|
PhaseName: phase.Name,
|
||||||
ExecutorDocument: executorDoc,
|
ExecutorDocument: executorDoc,
|
||||||
AirshipSettings: p.AirshipCTLSettings,
|
AirshipSettings: p.AirshipCTLSettings,
|
||||||
KubeConfig: kubeConfig,
|
KubeConfig: kubeConfig,
|
||||||
|
ClusterName: phase.ClusterName,
|
||||||
|
ClusterMap: cMap,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
pkg/phase/testdata/valid_site/phases/cluster_map.yaml
vendored
Normal file
9
pkg/phase/testdata/valid_site/phases/cluster_map.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: airshipit.org/v1alpha1
|
||||||
|
kind: ClusterMap
|
||||||
|
metadata:
|
||||||
|
name: clusterctl-v1
|
||||||
|
plan:
|
||||||
|
target:
|
||||||
|
parent: ephemeral
|
||||||
|
dynamicKubeConf: false
|
||||||
|
ephemeral: {}
|
||||||
@@ -5,3 +5,4 @@ resources:
|
|||||||
- capi_init.yaml
|
- capi_init.yaml
|
||||||
- clusterctl.yaml
|
- clusterctl.yaml
|
||||||
- kubernetes_apply.yaml
|
- kubernetes_apply.yaml
|
||||||
|
- cluster_map.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user