Add support for custom kubeconfig contexts
This commit extends cluster map interface to be able to specify a custom kubeconfig context per cluster in ClusterMap Related-To: #380 Related-To: #375 Closes: #380 Change-Id: I9a8a26e3a3666e069c243e871f89ae9222228f17
This commit is contained in:
parent
cb5849d024
commit
8567ddf749
@ -35,6 +35,8 @@ type Cluster struct {
|
||||
// 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"`
|
||||
// KubeconfigContext is the context in kubeconfig, default is equals to clusterMap key
|
||||
KubeconfigContext string `json:"kubeconfigContext,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultClusterMap can be used to safely unmarshal ClusterMap object without nil pointers
|
||||
|
@ -27,6 +27,7 @@ type ClusterMap interface {
|
||||
AllClusters() []string
|
||||
DynamicKubeConfig(string) bool
|
||||
ClusterNamespace(string) (string, error)
|
||||
ClusterKubeconfigContext(string) (string, error)
|
||||
}
|
||||
|
||||
// clusterMap allows to view clusters and relationship between them
|
||||
@ -77,3 +78,21 @@ func (cm clusterMap) AllClusters() []string {
|
||||
func (cm clusterMap) ClusterNamespace(clusterName string) (string, error) {
|
||||
return "default", nil
|
||||
}
|
||||
|
||||
// ClusterNamespace a namespace for given cluster
|
||||
// TODO implement how to get namespace for cluster
|
||||
func (cm clusterMap) ClusterKubeconfigContext(clusterName string) (string, error) {
|
||||
cluster, exists := cm.apiMap.Map[clusterName]
|
||||
|
||||
if !exists {
|
||||
return "", ErrClusterNotInMap{Map: cm.apiMap, Child: clusterName}
|
||||
}
|
||||
|
||||
kubeContext := cluster.KubeconfigContext
|
||||
// if kubeContext is still empty, set it to clusterName
|
||||
if kubeContext == "" {
|
||||
kubeContext = clusterName
|
||||
}
|
||||
|
||||
return kubeContext, nil
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ func TestClusterMap(t *testing.T) {
|
||||
targetCluster := "target"
|
||||
ephemeraCluster := "ephemeral"
|
||||
workloadCluster := "workload"
|
||||
workloadClusterKubeconfigContext := "different-workload-context"
|
||||
workloadClusterNoParent := "workload without parent"
|
||||
apiMap := &v1alpha1.ClusterMap{
|
||||
Map: map[string]*v1alpha1.Cluster{
|
||||
@ -39,6 +40,7 @@ func TestClusterMap(t *testing.T) {
|
||||
workloadCluster: {
|
||||
Parent: targetCluster,
|
||||
DynamicKubeConfig: true,
|
||||
KubeconfigContext: workloadClusterKubeconfigContext,
|
||||
},
|
||||
workloadClusterNoParent: {
|
||||
DynamicKubeConfig: true,
|
||||
@ -87,4 +89,21 @@ func TestClusterMap(t *testing.T) {
|
||||
clusters := cMap.AllClusters()
|
||||
assert.Len(t, clusters, 4)
|
||||
})
|
||||
|
||||
t.Run("kubeconfig context", func(t *testing.T) {
|
||||
kubeContext, err := cMap.ClusterKubeconfigContext(workloadCluster)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, workloadClusterKubeconfigContext, kubeContext)
|
||||
})
|
||||
|
||||
t.Run("kubeconfig default context", func(t *testing.T) {
|
||||
kubeContext, err := cMap.ClusterKubeconfigContext(targetCluster)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, targetCluster, kubeContext)
|
||||
})
|
||||
|
||||
t.Run("kubeconfig context error", func(t *testing.T) {
|
||||
_, err := cMap.ClusterKubeconfigContext("does not exist")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -95,7 +95,17 @@ func (c *ClusterctlExecutor) move(opts ifc.RunOptions, evtCh chan events.Event)
|
||||
return
|
||||
}
|
||||
defer cleanup()
|
||||
fromContext, err := c.clusterMap.ParentCluster(c.clusterName)
|
||||
fromCluster, err := c.clusterMap.ParentCluster(c.clusterName)
|
||||
if err != nil {
|
||||
c.handleErr(err, evtCh)
|
||||
return
|
||||
}
|
||||
fromContext, err := c.clusterMap.ClusterKubeconfigContext(fromCluster)
|
||||
if err != nil {
|
||||
c.handleErr(err, evtCh)
|
||||
return
|
||||
}
|
||||
toContext, err := c.clusterMap.ClusterKubeconfigContext(c.clusterName)
|
||||
if err != nil {
|
||||
c.handleErr(err, evtCh)
|
||||
return
|
||||
@ -104,7 +114,7 @@ func (c *ClusterctlExecutor) move(opts ifc.RunOptions, evtCh chan events.Event)
|
||||
log.Print("command 'clusterctl move' is going to be executed")
|
||||
// TODO (kkalynovskyi) add more details to dry-run, for now if dry run is set we skip move command
|
||||
if !opts.DryRun {
|
||||
err = c.Move(kubeConfigFile, fromContext, kubeConfigFile, c.clusterName, ns)
|
||||
err = c.Move(kubeConfigFile, fromContext, kubeConfigFile, toContext, ns)
|
||||
if err != nil {
|
||||
c.handleErr(err, evtCh)
|
||||
}
|
||||
@ -138,8 +148,15 @@ func (c *ClusterctlExecutor) init(opts ifc.RunOptions, evtCh chan events.Event)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
context, err := c.clusterMap.ClusterKubeconfigContext(c.clusterName)
|
||||
if err != nil {
|
||||
c.handleErr(err, evtCh)
|
||||
return
|
||||
}
|
||||
|
||||
// Use cluster name as context in kubeconfig file
|
||||
err = c.Init(kubeConfigFile, c.clusterName)
|
||||
err = c.Init(kubeConfigFile, context)
|
||||
if err != nil {
|
||||
c.handleErr(err, evtCh)
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/cluster/clustermap"
|
||||
cctlclient "opendev.org/airship/airshipctl/pkg/clusterctl/client"
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
@ -104,6 +106,7 @@ func TestExecutorRun(t *testing.T) {
|
||||
fs document.FileSystem
|
||||
bundlePath string
|
||||
expectedEvt []events.Event
|
||||
clusterMap clustermap.ClusterMap
|
||||
}{
|
||||
{
|
||||
name: "Error unknown action",
|
||||
@ -112,6 +115,7 @@ func TestExecutorRun(t *testing.T) {
|
||||
expectedEvt: []events.Event{
|
||||
wrapError(cctlclient.ErrUnknownExecutorAction{Action: "someAction"}),
|
||||
},
|
||||
clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()),
|
||||
},
|
||||
{
|
||||
name: "Error temporary file",
|
||||
@ -128,6 +132,7 @@ func TestExecutorRun(t *testing.T) {
|
||||
}),
|
||||
wrapError(errTmpFile),
|
||||
},
|
||||
clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()),
|
||||
},
|
||||
{
|
||||
name: "Regular Run init",
|
||||
@ -143,6 +148,7 @@ func TestExecutorRun(t *testing.T) {
|
||||
MockRemoveAll: func() error { return nil },
|
||||
},
|
||||
bundlePath: "testdata/executor_init",
|
||||
clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()),
|
||||
expectedEvt: []events.Event{
|
||||
events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{
|
||||
Operation: events.ClusterctlInitStart,
|
||||
@ -166,6 +172,7 @@ func TestExecutorRun(t *testing.T) {
|
||||
ExecutorDocument: tt.cfgDoc,
|
||||
Helper: makeDefaultHelper(t),
|
||||
KubeConfig: kubeCfg,
|
||||
ClusterMap: tt.clusterMap,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
ch := make(chan events.Event)
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/cluster/clustermap"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
"opendev.org/airship/airshipctl/pkg/events"
|
||||
@ -40,6 +41,7 @@ type ExecutorOptions struct {
|
||||
BundleFactory document.BundleFactoryFunc
|
||||
Kubeconfig kubeconfig.Interface
|
||||
Helper ifc.Helper
|
||||
ClusterMap clustermap.ClusterMap
|
||||
}
|
||||
|
||||
var _ ifc.Executor = &Executor{}
|
||||
@ -64,6 +66,7 @@ func registerExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) {
|
||||
ExecutorDocument: cfg.ExecutorDocument,
|
||||
BundleFactory: cfg.BundleFactory,
|
||||
Kubeconfig: cfg.KubeConfig,
|
||||
ClusterMap: cfg.ClusterMap,
|
||||
})
|
||||
}
|
||||
|
||||
@ -123,6 +126,11 @@ func (e *Executor) Run(ch chan events.Event, runOpts ifc.RunOptions) {
|
||||
}
|
||||
|
||||
func (e *Executor) prepareApplier(ch chan events.Event) (*Applier, document.Bundle, error) {
|
||||
log.Debug("Getting kubeconfig context name from cluster map")
|
||||
context, err := e.Options.ClusterMap.ClusterKubeconfigContext(e.Options.ClusterName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Debug("Getting kubeconfig file information from kubeconfig provider")
|
||||
path, cleanup, err := e.Options.Kubeconfig.GetFile()
|
||||
if err != nil {
|
||||
@ -136,8 +144,8 @@ func (e *Executor) prepareApplier(ch chan events.Event) (*Applier, document.Bund
|
||||
}
|
||||
// set up cleanup only if all calls up to here were successful
|
||||
e.cleanup = cleanup
|
||||
// Use cluster name as context in kubeconfig file
|
||||
factory := utils.FactoryFromKubeConfig(path, e.Options.ClusterName)
|
||||
log.Debugf("Using kubeconfig at '%s' and context '%s'", path, context)
|
||||
factory := utils.FactoryFromKubeConfig(path, context)
|
||||
return NewApplier(ch, factory), bundle, nil
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/cluster/clustermap"
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/events"
|
||||
@ -153,11 +155,13 @@ func TestExecutorRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
containsErr string
|
||||
clusterName string
|
||||
|
||||
kubeconf kubeconfig.Interface
|
||||
execDoc document.Document
|
||||
bundleFactory document.BundleFactoryFunc
|
||||
helper ifc.Helper
|
||||
clusterMap clustermap.ClusterMap
|
||||
}{
|
||||
{
|
||||
name: "cant read kubeconfig error",
|
||||
@ -166,6 +170,21 @@ func TestExecutorRun(t *testing.T) {
|
||||
bundleFactory: testBundleFactory("testdata/source_bundle"),
|
||||
kubeconf: testKubeconfig(`invalid kubeconfig`),
|
||||
execDoc: toKubernetesApply(t, ValidExecutorDocNamespaced),
|
||||
clusterName: "ephemeral-cluster",
|
||||
clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{
|
||||
Map: map[string]*v1alpha1.Cluster{
|
||||
"ephemeral-cluster": {},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "error cluster not defined",
|
||||
containsErr: "cluster is not defined in in cluster map",
|
||||
helper: makeDefaultHelper(t),
|
||||
bundleFactory: testBundleFactory("testdata/source_bundle"),
|
||||
kubeconf: testKubeconfig(testValidKubeconfig),
|
||||
execDoc: toKubernetesApply(t, ValidExecutorDocNamespaced),
|
||||
clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -177,6 +196,8 @@ func TestExecutorRun(t *testing.T) {
|
||||
Helper: tt.helper,
|
||||
BundleFactory: tt.bundleFactory,
|
||||
Kubeconfig: tt.kubeconf,
|
||||
ClusterMap: tt.clusterMap,
|
||||
ClusterName: tt.clusterName,
|
||||
})
|
||||
if tt.name == "Nil bundle provided" {
|
||||
require.Error(t, err)
|
||||
|
Loading…
Reference in New Issue
Block a user