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