Allow to specify multiple clusters per get-kubeconfig request

Change-Id: I1b736a4b9cae4e6e47ddb7909a8fd619518e975c
Signed-off-by: Ruslan Aliev <raliev@mirantis.com>
Closes: #567
This commit is contained in:
Ruslan Aliev 2021-06-04 16:47:28 -05:00
parent f74a935df6
commit b8ddc1fe43
10 changed files with 64 additions and 63 deletions

View File

@ -23,11 +23,14 @@ import (
const (
getKubeconfigLong = `
Retrieves kubeconfig of the cluster and prints it to stdout.
Retrieves kubeconfig of the cluster(s) and prints it to stdout.
If you specify CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and
If you specify single CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and
will have its context defined.
If you specify multiple CLUSTER_NAME args, kubeconfig will contain contexts for all of them, but current one
won't be specified.
If you don't specify CLUSTER_NAME, kubeconfig will have multiple contexts for every cluster
in the airship site. Context names will correspond to cluster names. CurrentContext will be empty.
`
@ -52,8 +55,8 @@ Airshipctl will overwrite the contents of the file, if you want merge with exist
func NewGetKubeconfigCommand(cfgFactory config.Factory) *cobra.Command {
opts := &cluster.GetKubeconfigCommand{}
cmd := &cobra.Command{
Use: "get-kubeconfig CLUSTER_NAME",
Short: "Airshipctl command to retrieve kubeconfig for a desired cluster",
Use: "get-kubeconfig [CLUSTER_NAME...]",
Short: "Airshipctl command to retrieve kubeconfig for a desired cluster(s)",
Long: getKubeconfigLong[1:],
Args: GetKubeconfArgs(opts),
Example: getKubeconfigExample,
@ -82,9 +85,10 @@ func NewGetKubeconfigCommand(cfgFactory config.Factory) *cobra.Command {
// GetKubeconfArgs extracts one or less arguments from command line, and saves it as name
func GetKubeconfArgs(opts *cluster.GetKubeconfigCommand) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
opts.ClusterName = args[0]
for _, arg := range args {
opts.ClusterNames = append(opts.ClusterNames, arg)
}
return cobra.MaximumNArgs(1)(cmd, args)
return cobra.MinimumNArgs(0)(cmd, args)
}
}

View File

@ -43,20 +43,20 @@ func TestGetKubeconfArgs(t *testing.T) {
name string
args []string
expectedErrStr string
expectedClusterName string
expectedClusterNames []string
}{
{
name: "success one cluster specified",
args: []string{"cluster01"},
expectedClusterName: "cluster01",
expectedClusterNames: []string{"cluster01"},
},
{
name: "success no cluster specified",
},
{
name: "error two cluster specified",
expectedErrStr: "accepts at most 1 arg(s)",
name: "success two cluster specified",
args: []string{"cluster01", "cluster02"},
expectedClusterNames: []string{"cluster01", "cluster02"},
},
}
for _, tt := range tests {
@ -70,7 +70,7 @@ func TestGetKubeconfArgs(t *testing.T) {
assert.Contains(t, err.Error(), tt.expectedErrStr)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedClusterName, cmd.ClusterName)
assert.Equal(t, tt.expectedClusterNames, cmd.ClusterNames)
}
})
}

View File

@ -5,7 +5,7 @@ Usage:
cluster [command]
Available Commands:
get-kubeconfig Airshipctl command to retrieve kubeconfig for a desired cluster
get-kubeconfig Airshipctl command to retrieve kubeconfig for a desired cluster(s)
help Help about any command
list Airshipctl command to get and list defined clusters
status Retrieve statuses of deployed cluster components

View File

@ -1,13 +1,16 @@
Retrieves kubeconfig of the cluster and prints it to stdout.
Retrieves kubeconfig of the cluster(s) and prints it to stdout.
If you specify CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and
If you specify single CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and
will have its context defined.
If you specify multiple CLUSTER_NAME args, kubeconfig will contain contexts for all of them, but current one
won't be specified.
If you don't specify CLUSTER_NAME, kubeconfig will have multiple contexts for every cluster
in the airship site. Context names will correspond to cluster names. CurrentContext will be empty.
Usage:
get-kubeconfig CLUSTER_NAME [flags]
get-kubeconfig [CLUSTER_NAME...] [flags]
Examples:

View File

@ -32,7 +32,7 @@ SEE ALSO
~~~~~~~~
* :ref:`airshipctl <airshipctl>` - A unified command line tool for management of end-to-end kubernetes cluster deployment on cloud infrastructure environments.
* :ref:`airshipctl cluster get-kubeconfig <airshipctl_cluster_get-kubeconfig>` - Airshipctl command to retrieve kubeconfig for a desired cluster
* :ref:`airshipctl cluster get-kubeconfig <airshipctl_cluster_get-kubeconfig>` - Airshipctl command to retrieve kubeconfig for a desired cluster(s)
* :ref:`airshipctl cluster list <airshipctl_cluster_list>` - Airshipctl command to get and list defined clusters
* :ref:`airshipctl cluster status <airshipctl_cluster_status>` - Retrieve statuses of deployed cluster components

View File

@ -3,24 +3,27 @@
airshipctl cluster get-kubeconfig
---------------------------------
Airshipctl command to retrieve kubeconfig for a desired cluster
Airshipctl command to retrieve kubeconfig for a desired cluster(s)
Synopsis
~~~~~~~~
Retrieves kubeconfig of the cluster and prints it to stdout.
Retrieves kubeconfig of the cluster(s) and prints it to stdout.
If you specify CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and
If you specify single CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and
will have its context defined.
If you specify multiple CLUSTER_NAME args, kubeconfig will contain contexts for all of them, but current one
won't be specified.
If you don't specify CLUSTER_NAME, kubeconfig will have multiple contexts for every cluster
in the airship site. Context names will correspond to cluster names. CurrentContext will be empty.
::
airshipctl cluster get-kubeconfig CLUSTER_NAME [flags]
airshipctl cluster get-kubeconfig [CLUSTER_NAME...] [flags]
Examples
~~~~~~~~

View File

@ -56,7 +56,7 @@ func StatusRunner(o StatusOptions, w io.Writer) error {
// GetKubeconfigCommand holds options for get kubeconfig command
type GetKubeconfigCommand struct {
ClusterName string
ClusterNames []string
File string
Merge bool
}
@ -80,14 +80,14 @@ func (cmd *GetKubeconfigCommand) RunE(cfgFactory config.Factory, writer io.Write
}
var siteWide bool
if cmd.ClusterName == "" {
if len(cmd.ClusterNames) == 0 {
siteWide = true
}
kubeconf := kubeconfig.NewBuilder().
WithBundle(helper.PhaseConfigBundle()).
WithClusterMap(cMap).
WithClusterName(cmd.ClusterName).
WithClusterNames(cmd.ClusterNames...).
WithTempRoot(helper.WorkDir()).
SiteWide(siteWide).
Build()

View File

@ -40,7 +40,7 @@ func NewBuilder() *Builder {
// such as path to kubeconfig, path to bundle that should contain kubeconfig and parent cluster
type Builder struct {
siteWide bool
clusterName string
clusterNames []string
root string
bundle document.Bundle
@ -62,9 +62,9 @@ func (b *Builder) WithClusterMap(cMap clustermap.ClusterMap) *Builder {
return b
}
// WithClusterName allows to reach to a cluster to download kubeconfig from there
func (b *Builder) WithClusterName(clusterName string) *Builder {
b.clusterName = clusterName
// WithClusterNames allows to reach to a cluster to download kubeconfig from there
func (b *Builder) WithClusterNames(clusterNames ...string) *Builder {
b.clusterNames = clusterNames
return b
}
@ -106,40 +106,31 @@ func (b *Builder) Build() Interface {
}
func (b *Builder) build() ([]byte, error) {
// Set current context to clustername if it was provided
var result *api.Config
var err error
if !b.siteWide {
var kubeContext string
kubeContext, result, err = b.buildOne(b.clusterName)
if err != nil {
if err := b.buildKubeconfig(); err != nil {
return nil, err
}
b.siteKubeconf.CurrentContext = kubeContext
} else {
result, err = b.builtSiteKubeconf()
if err != nil {
return nil, err
}
}
return clientcmd.Write(*result)
}
func (b *Builder) builtSiteKubeconf() (*api.Config, error) {
log.Debugf("Getting site kubeconfig")
for _, clusterID := range b.clusterMap.AllClusters() {
log.Debugf("Getting kubeconfig for cluster '%s' to build site kubeconfig", clusterID)
return clientcmd.Write(*b.siteKubeconf)
}
func (b *Builder) buildKubeconfig() error {
log.Debugf("Getting requested kubeconfig")
for _, clusterID := range (map[bool][]string{true: b.clusterMap.AllClusters(), false: b.clusterNames})[b.siteWide] {
log.Debugf("Getting kubeconfig for cluster '%s'", clusterID)
// buildOne merges context into site kubeconfig
_, _, err := b.buildOne(clusterID)
ctx, _, err := b.buildOne(clusterID)
if !b.siteWide && len(b.clusterNames) == 1 {
b.siteKubeconf.CurrentContext = ctx
}
if IsErrAllSourcesFailedErr(err) {
log.Debugf("All kubeconfig sources failed for cluster '%s', error '%v', skipping it",
clusterID, err)
continue
} else if err != nil {
return nil, err
return err
}
}
return b.siteKubeconf, nil
return nil
}
func (b *Builder) buildOne(clusterID string) (string, *api.Config, error) {

View File

@ -260,7 +260,7 @@ func TestBuilderClusterctl(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
kube := kubeconfig.NewBuilder().
WithClusterMap(tt.clusterMap).
WithClusterName(tt.requestedClusterName).
WithClusterNames(tt.requestedClusterName).
WithBundle(testBundle).
WithTempRoot(tt.tempRoot).
WithCoreV1Client(tt.client).

View File

@ -105,7 +105,7 @@ func (p *phase) executor(docFactory document.DocFactoryFunc,
WithBundle(p.helper.PhaseConfigBundle()).
WithClusterMap(cMap).
WithTempRoot(p.helper.WorkDir()).
WithClusterName(p.apiObj.ClusterName).
WithClusterNames(p.apiObj.ClusterName).
SiteWide(p.apiObj.Config.SiteWideKubeconfig).
Build()