diff --git a/cmd/cluster/get_kubeconfig.go b/cmd/cluster/get_kubeconfig.go index 33f851f87..5774879f0 100644 --- a/cmd/cluster/get_kubeconfig.go +++ b/cmd/cluster/get_kubeconfig.go @@ -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) } } diff --git a/cmd/cluster/get_kubeconfig_test.go b/cmd/cluster/get_kubeconfig_test.go index 9a6accd13..e57f08696 100644 --- a/cmd/cluster/get_kubeconfig_test.go +++ b/cmd/cluster/get_kubeconfig_test.go @@ -40,23 +40,23 @@ func TestNewKubeConfigCommandCmd(t *testing.T) { func TestGetKubeconfArgs(t *testing.T) { tests := []struct { - name string - args []string - expectedErrStr string - expectedClusterName string + name string + args []string + expectedErrStr string + expectedClusterNames []string }{ { - name: "success one cluster specified", - args: []string{"cluster01"}, - expectedClusterName: "cluster01", + name: "success one cluster specified", + args: []string{"cluster01"}, + expectedClusterNames: []string{"cluster01"}, }, { name: "success no cluster specified", }, { - name: "error two cluster specified", - expectedErrStr: "accepts at most 1 arg(s)", - args: []string{"cluster01", "cluster02"}, + 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) } }) } diff --git a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden index df9aac4ed..4bc97b7d7 100644 --- a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden +++ b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden @@ -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 diff --git a/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden b/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden index 394b7cd8a..ac9c45a15 100644 --- a/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden +++ b/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden @@ -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: diff --git a/docs/source/cli/cluster/airshipctl_cluster.rst b/docs/source/cli/cluster/airshipctl_cluster.rst index 4c46ea64e..e9abef0e9 100644 --- a/docs/source/cli/cluster/airshipctl_cluster.rst +++ b/docs/source/cli/cluster/airshipctl_cluster.rst @@ -32,7 +32,7 @@ SEE ALSO ~~~~~~~~ * :ref:`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 command to retrieve kubeconfig for a desired cluster +* :ref:`airshipctl cluster get-kubeconfig ` - Airshipctl command to retrieve kubeconfig for a desired cluster(s) * :ref:`airshipctl cluster list ` - Airshipctl command to get and list defined clusters * :ref:`airshipctl cluster status ` - Retrieve statuses of deployed cluster components diff --git a/docs/source/cli/cluster/airshipctl_cluster_get-kubeconfig.rst b/docs/source/cli/cluster/airshipctl_cluster_get-kubeconfig.rst index 72c0ed381..741c733b2 100644 --- a/docs/source/cli/cluster/airshipctl_cluster_get-kubeconfig.rst +++ b/docs/source/cli/cluster/airshipctl_cluster_get-kubeconfig.rst @@ -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 ~~~~~~~~ diff --git a/pkg/cluster/command.go b/pkg/cluster/command.go index 30480b924..21c8a539e 100755 --- a/pkg/cluster/command.go +++ b/pkg/cluster/command.go @@ -56,9 +56,9 @@ func StatusRunner(o StatusOptions, w io.Writer) error { // GetKubeconfigCommand holds options for get kubeconfig command type GetKubeconfigCommand struct { - ClusterName string - File string - Merge bool + ClusterNames []string + File string + Merge bool } // RunE creates new kubeconfig interface object from secret, options hold the writer and 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() diff --git a/pkg/k8s/kubeconfig/builder.go b/pkg/k8s/kubeconfig/builder.go index f5a7f2cbc..1e0055c88 100644 --- a/pkg/k8s/kubeconfig/builder.go +++ b/pkg/k8s/kubeconfig/builder.go @@ -39,9 +39,9 @@ func NewBuilder() *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 { - siteWide bool - clusterName string - root string + siteWide bool + clusterNames []string + root string bundle document.Bundle client corev1.CoreV1Interface @@ -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 { - return nil, err - } - b.siteKubeconf.CurrentContext = kubeContext - } else { - result, err = b.builtSiteKubeconf() - if err != nil { - return nil, err - } + if err := b.buildKubeconfig(); err != nil { + return nil, err } - return clientcmd.Write(*result) + + return clientcmd.Write(*b.siteKubeconf) } -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) +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) { diff --git a/pkg/k8s/kubeconfig/builder_test.go b/pkg/k8s/kubeconfig/builder_test.go index 79fff08e9..a91a5f220 100644 --- a/pkg/k8s/kubeconfig/builder_test.go +++ b/pkg/k8s/kubeconfig/builder_test.go @@ -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). diff --git a/pkg/phase/client.go b/pkg/phase/client.go index 2bfef7197..579670077 100644 --- a/pkg/phase/client.go +++ b/pkg/phase/client.go @@ -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()