diff --git a/cmd/cluster/cluster.go b/cmd/cluster/cluster.go index 67b0943e3..707ae8691 100644 --- a/cmd/cluster/cluster.go +++ b/cmd/cluster/cluster.go @@ -44,6 +44,7 @@ func NewClusterCommand(cfgFactory config.Factory) *cobra.Command { clusterRootCmd.AddCommand(resetsatoken.NewResetCommand(cfgFactory)) clusterRootCmd.AddCommand(checkexpiration.NewCheckCommand(cfgFactory)) clusterRootCmd.AddCommand(NewGetKubeconfigCommand(cfgFactory)) + clusterRootCmd.AddCommand(NewListCommand(cfgFactory)) return clusterRootCmd } diff --git a/cmd/cluster/list.go b/cmd/cluster/list.go new file mode 100755 index 000000000..9243f7d79 --- /dev/null +++ b/cmd/cluster/list.go @@ -0,0 +1,52 @@ +/* + 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 cluster + +import ( + "github.com/spf13/cobra" + + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/phase" +) + +const ( + listShort = "Retrieve the list of defined clusters" + listExample = ` +# Retrieve cluster list +airshipctl cluster list --airshipconf /tmp/airconfig +` +) + +// NewListCommand creates a command which retrieves list of clusters +func NewListCommand(cfgFactory config.Factory) *cobra.Command { + o := &phase.ClusterListCommand{Factory: cfgFactory} + cmd := &cobra.Command{ + Use: "list", + Short: listShort, + Example: listExample[1:], + RunE: listRunE(o), + } + + return cmd +} + +// listRunE returns a function to cobra command to be executed in runtime +func listRunE(o *phase.ClusterListCommand) func( + cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + o.Writer = cmd.OutOrStdout() + return o.RunE() + } +} diff --git a/cmd/cluster/list_test.go b/cmd/cluster/list_test.go new file mode 100755 index 000000000..f66933bba --- /dev/null +++ b/cmd/cluster/list_test.go @@ -0,0 +1,35 @@ +/* + 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 cluster_test + +import ( + "testing" + + "opendev.org/airship/airshipctl/cmd/cluster" + "opendev.org/airship/airshipctl/testutil" +) + +func TestNewListCommand(t *testing.T) { + tests := []*testutil.CmdTest{ + { + Name: "cluster-list-cmd-with-help", + CmdLine: "--help", + Cmd: cluster.NewListCommand(nil), + }, + } + for _, testcase := range tests { + testutil.RunTest(t, testcase) + } +} diff --git a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden index c4f83dfa0..0448597b1 100644 --- a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden +++ b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden @@ -9,6 +9,7 @@ Available Commands: get-kubeconfig Retrieve kubeconfig for a desired cluster help Help about any command init Deploy cluster-api provider components + list Retrieve the list of defined clusters move Move Cluster API objects, provider specific objects and all dependencies to the target cluster rotate-sa-token Rotate tokens of Service Accounts status Retrieve statuses of deployed cluster components diff --git a/cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden b/cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden new file mode 100644 index 000000000..9cd4ebb9f --- /dev/null +++ b/cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden @@ -0,0 +1,12 @@ +Retrieve the list of defined clusters + +Usage: + list [flags] + +Examples: +# Retrieve cluster list +airshipctl cluster list --airshipconf /tmp/airconfig + + +Flags: + -h, --help help for list diff --git a/docs/source/cli/airshipctl_cluster.md b/docs/source/cli/airshipctl_cluster.md index 5138e84a2..d7802462e 100644 --- a/docs/source/cli/airshipctl_cluster.md +++ b/docs/source/cli/airshipctl_cluster.md @@ -27,6 +27,7 @@ such as getting status and deploying initial infrastructure. * [airshipctl cluster check-certificate-expiration](airshipctl_cluster_check-certificate-expiration.md) - Check for expiring TLS certificates, secrets and kubeconfigs in the kubernetes cluster * [airshipctl cluster get-kubeconfig](airshipctl_cluster_get-kubeconfig.md) - Retrieve kubeconfig for a desired cluster * [airshipctl cluster init](airshipctl_cluster_init.md) - Deploy cluster-api provider components +* [airshipctl cluster list](airshipctl_cluster_list.md) - Retrieve the list of defined clusters * [airshipctl cluster move](airshipctl_cluster_move.md) - Move Cluster API objects, provider specific objects and all dependencies to the target cluster * [airshipctl cluster rotate-sa-token](airshipctl_cluster_rotate-sa-token.md) - Rotate tokens of Service Accounts * [airshipctl cluster status](airshipctl_cluster_status.md) - Retrieve statuses of deployed cluster components diff --git a/docs/source/cli/airshipctl_cluster_list.md b/docs/source/cli/airshipctl_cluster_list.md new file mode 100644 index 000000000..4cf7acd04 --- /dev/null +++ b/docs/source/cli/airshipctl_cluster_list.md @@ -0,0 +1,37 @@ +## airshipctl cluster list + +Retrieve the list of defined clusters + +### Synopsis + +Retrieve the list of defined clusters + +``` +airshipctl cluster list [flags] +``` + +### Examples + +``` +# Retrieve cluster list +airshipctl cluster list --airshipconf /tmp/airconfig + +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config") + --debug enable verbose output +``` + +### SEE ALSO + +* [airshipctl cluster](airshipctl_cluster.md) - Manage Kubernetes clusters + diff --git a/pkg/phase/command.go b/pkg/phase/command.go index c79066816..245ea0028 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -244,3 +244,33 @@ func (c *PlanRunCommand) RunE() error { } return plan.Run(ifc.RunOptions{DryRun: c.Options.DryRun, Timeout: c.Options.Timeout, Progress: c.Options.Progress}) } + +// ClusterListCommand options for cluster list command +type ClusterListCommand struct { + Factory config.Factory + Writer io.Writer +} + +// RunE executes cluster list command +func (c *ClusterListCommand) RunE() error { + cfg, err := c.Factory() + if err != nil { + return err + } + helper, err := NewHelper(cfg) + if err != nil { + return err + } + clusterMap, err := helper.ClusterMap() + if err != nil { + return err + } + + clusterList := clusterMap.AllClusters() + for _, clusterName := range clusterList { + if _, err := c.Writer.Write([]byte(clusterName + "\n")); err != nil { + return err + } + } + return nil +} diff --git a/pkg/phase/command_test.go b/pkg/phase/command_test.go index ae067fafd..e25d06b26 100644 --- a/pkg/phase/command_test.go +++ b/pkg/phase/command_test.go @@ -28,9 +28,10 @@ import ( ) const ( - testFactoryErr = "test config error" - testNewHelperErr = "Missing configuration" - testNoBundlePath = "no such file or directory" + testFactoryErr = "test config error" + testNewHelperErr = "Missing configuration" + testNoBundlePath = "no such file or directory" + defaultCurrentContext = "context" ) func TestRunCommand(t *testing.T) { @@ -73,7 +74,7 @@ func TestRunCommand(t *testing.T) { }, }, } - conf.CurrentContext = "context" + conf.CurrentContext = defaultCurrentContext conf.Contexts = map[string]*config.Context{ "context": { Manifest: "manifest", @@ -143,7 +144,7 @@ func TestListCommand(t *testing.T) { }, }, } - conf.CurrentContext = "context" + conf.CurrentContext = defaultCurrentContext conf.Contexts = map[string]*config.Context{ "context": { Manifest: "manifest", @@ -211,7 +212,7 @@ func TestTreeCommand(t *testing.T) { }, }, } - conf.CurrentContext = "context" + conf.CurrentContext = defaultCurrentContext conf.Contexts = map[string]*config.Context{ "context": { Manifest: "manifest", @@ -334,7 +335,7 @@ func TestPlanRunCommand(t *testing.T) { }, }, } - conf.CurrentContext = "context" + conf.CurrentContext = defaultCurrentContext conf.Contexts = map[string]*config.Context{ "context": { Manifest: "manifest", @@ -367,3 +368,72 @@ func TestPlanRunCommand(t *testing.T) { }) } } + +func TestClusterListCommand_RunE(t *testing.T) { + testErr := fmt.Errorf(testFactoryErr) + testCases := []struct { + name string + factory config.Factory + expectedErr string + }{ + { + name: "Error config factory", + factory: func() (*config.Config, error) { + return nil, testErr + }, + expectedErr: testFactoryErr, + }, + { + name: "Error new helper", + factory: func() (*config.Config, error) { + return &config.Config{ + CurrentContext: "does not exist", + Contexts: make(map[string]*config.Context), + }, nil + }, + expectedErr: "Missing configuration: Context with name 'does not exist'", + }, + { + name: "No error", + factory: func() (*config.Config, error) { + conf := config.NewConfig() + conf.Manifests = map[string]*config.Manifest{ + "manifest": { + MetadataPath: "metadata.yaml", + TargetPath: "testdata", + PhaseRepositoryName: config.DefaultTestPhaseRepo, + Repositories: map[string]*config.Repository{ + config.DefaultTestPhaseRepo: { + URLString: "", + }, + }, + }, + } + conf.CurrentContext = defaultCurrentContext + conf.Contexts = map[string]*config.Context{ + "context": { + Manifest: "manifest", + }, + } + return conf, nil + }, + expectedErr: "", + }, + } + for _, tc := range testCases { + tt := tc + t.Run(tt.name, func(t *testing.T) { + cmd := phase.ClusterListCommand{ + Factory: tt.factory, + Writer: bytes.NewBuffer(nil), + } + err := cmd.RunE() + if tt.expectedErr != "" { + require.Error(t, err) + assert.Equal(t, tt.expectedErr, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +}