Merge "[#323] Cluster list command to output in table/clusternames."
This commit is contained in:
commit
6a62e11fea
@ -26,6 +26,8 @@ const (
|
||||
listExample = `
|
||||
# Retrieve cluster list
|
||||
airshipctl cluster list --airshipconf /tmp/airconfig
|
||||
airshipctl cluster list -o table
|
||||
airshipctl cluster list -o name
|
||||
`
|
||||
)
|
||||
|
||||
@ -38,6 +40,11 @@ func NewListCommand(cfgFactory config.Factory) *cobra.Command {
|
||||
Example: listExample[1:],
|
||||
RunE: listRunE(o),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&o.Format,
|
||||
"output", "o", "name", "'table' "+
|
||||
"and 'name' are available "+
|
||||
"output formats")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -6,7 +6,10 @@ Usage:
|
||||
Examples:
|
||||
# Retrieve cluster list
|
||||
airshipctl cluster list --airshipconf /tmp/airconfig
|
||||
airshipctl cluster list -o table
|
||||
airshipctl cluster list -o name
|
||||
|
||||
|
||||
Flags:
|
||||
-h, --help help for list
|
||||
-h, --help help for list
|
||||
-o, --output string 'table' and 'name' are available output formats (default "name")
|
||||
|
@ -15,13 +15,16 @@ airshipctl cluster list [flags]
|
||||
```
|
||||
# Retrieve cluster list
|
||||
airshipctl cluster list --airshipconf /tmp/airconfig
|
||||
airshipctl cluster list -o table
|
||||
airshipctl cluster list -o name
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for list
|
||||
-h, --help help for list
|
||||
-o, --output string 'table' and 'name' are available output formats (default "name")
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
@ -15,12 +15,22 @@
|
||||
package clustermap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
)
|
||||
|
||||
// DefaultClusterAPIObjNamespace is a default namespace used for cluster-api cluster object
|
||||
const DefaultClusterAPIObjNamespace = "default"
|
||||
|
||||
// WriteOptions has format in which we want to print the output(table/yaml/cluster name)
|
||||
type WriteOptions struct {
|
||||
Format string
|
||||
}
|
||||
|
||||
// ClusterMap interface that allows to list all clusters, find its parent, namespace,
|
||||
// check if dynamic kubeconfig is enabled.
|
||||
// TODO use typed cluster names
|
||||
@ -29,6 +39,7 @@ type ClusterMap interface {
|
||||
AllClusters() []string
|
||||
ClusterKubeconfigContext(string) (string, error)
|
||||
Sources(string) ([]v1alpha1.KubeconfigSource, error)
|
||||
Write(io.Writer, WriteOptions) error
|
||||
}
|
||||
|
||||
// clusterMap allows to view clusters and relationship between them
|
||||
@ -82,3 +93,28 @@ func (cm clusterMap) Sources(clusterName string) ([]v1alpha1.KubeconfigSource, e
|
||||
}
|
||||
return cluster.Sources, nil
|
||||
}
|
||||
|
||||
// Write prints the cluster list in table/name output format
|
||||
func (cm clusterMap) Write(writer io.Writer, wo WriteOptions) error {
|
||||
if wo.Format == "table" {
|
||||
w := tabwriter.NewWriter(os.Stdout, 20, 8, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tKUBECONFIG CONTEXT\tPARENT CLUSTER\n")
|
||||
for clustername, cluster := range cm.apiMap.Map {
|
||||
kubeconfig, err := cm.ClusterKubeconfigContext(clustername)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n",
|
||||
clustername, kubeconfig, cluster.Parent)
|
||||
}
|
||||
w.Flush()
|
||||
} else if wo.Format == "name" {
|
||||
clusterList := cm.AllClusters()
|
||||
for _, clusterName := range clusterList {
|
||||
if _, err := writer.Write([]byte(clusterName + "\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,11 @@
|
||||
package clustermap_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -65,7 +70,6 @@ func TestClusterMap(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cMap := clustermap.NewClusterMap(apiMap)
|
||||
require.NotNil(t, cMap)
|
||||
|
||||
@ -121,3 +125,64 @@ func TestClusterMap(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_clusterMap_Write(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
wr := bufio.NewWriter(&b)
|
||||
targetCluster := "target"
|
||||
ephemeraCluster := "ephemeral"
|
||||
apiMap := &v1alpha1.ClusterMap{
|
||||
Map: map[string]*v1alpha1.Cluster{
|
||||
targetCluster: {
|
||||
Parent: ephemeraCluster,
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
wo clustermap.WriteOptions
|
||||
wantWriter string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
writer io.Writer
|
||||
}{
|
||||
{
|
||||
name: "success table",
|
||||
wo: clustermap.WriteOptions{Format: "table"},
|
||||
expectedOut: "NAME KUBECONFIG CONTEXT PARENT CLUSTER" +
|
||||
"\ntarget target ephemeral\n",
|
||||
writer: wr,
|
||||
},
|
||||
{
|
||||
name: "writer nil",
|
||||
wo: clustermap.WriteOptions{Format: "table"},
|
||||
writer: nil,
|
||||
expectedOut: "",
|
||||
},
|
||||
}
|
||||
rStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
require.Error(t, err)
|
||||
}
|
||||
os.Stdout = w
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cMap := clustermap.NewClusterMap(apiMap)
|
||||
err := cMap.Write(tt.writer, tt.wo)
|
||||
w.Close()
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
out, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
require.Error(t, err)
|
||||
}
|
||||
os.Stdout = rStdout
|
||||
assert.Equal(t, tt.expectedOut, string(out))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"sigs.k8s.io/cli-utils/pkg/print/table"
|
||||
|
||||
"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"
|
||||
phaseerrors "opendev.org/airship/airshipctl/pkg/phase/errors"
|
||||
@ -247,10 +248,14 @@ func (c *PlanRunCommand) RunE() error {
|
||||
type ClusterListCommand struct {
|
||||
Factory config.Factory
|
||||
Writer io.Writer
|
||||
Format string
|
||||
}
|
||||
|
||||
// RunE executes cluster list command
|
||||
func (c *ClusterListCommand) RunE() error {
|
||||
if c.Format != "table" && c.Format != "name" {
|
||||
return phaseerrors.ErrInvalidOutputFormat{RequestedFormat: c.Format}
|
||||
}
|
||||
cfg, err := c.Factory()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -263,12 +268,9 @@ func (c *ClusterListCommand) RunE() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterList := clusterMap.AllClusters()
|
||||
for _, clusterName := range clusterList {
|
||||
if _, err := c.Writer.Write([]byte(clusterName + "\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
err = clusterMap.Write(c.Writer, clustermap.WriteOptions{Format: c.Format})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -456,6 +456,7 @@ func TestClusterListCommand_RunE(t *testing.T) {
|
||||
name string
|
||||
factory config.Factory
|
||||
expectedErr string
|
||||
Format string
|
||||
}{
|
||||
{
|
||||
name: "Error config factory",
|
||||
@ -463,6 +464,7 @@ func TestClusterListCommand_RunE(t *testing.T) {
|
||||
return nil, testErr
|
||||
},
|
||||
expectedErr: testFactoryErr,
|
||||
Format: "name",
|
||||
},
|
||||
{
|
||||
name: "Error new helper",
|
||||
@ -473,9 +475,11 @@ func TestClusterListCommand_RunE(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
expectedErr: "missing configuration: context with name 'does not exist'",
|
||||
Format: "name",
|
||||
},
|
||||
{
|
||||
name: "No error",
|
||||
name: "No error",
|
||||
Format: "name",
|
||||
factory: func() (*config.Config, error) {
|
||||
conf := config.NewConfig()
|
||||
conf.Manifests = map[string]*config.Manifest{
|
||||
@ -506,6 +510,7 @@ func TestClusterListCommand_RunE(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := phase.ClusterListCommand{
|
||||
Factory: tt.factory,
|
||||
Format: tt.Format,
|
||||
Writer: bytes.NewBuffer(nil),
|
||||
}
|
||||
err := cmd.RunE()
|
||||
|
@ -69,3 +69,12 @@ type ErrInvalidPhase struct {
|
||||
func (e ErrInvalidPhase) Error() string {
|
||||
return fmt.Sprintf("invalid phase: %s", e.Reason)
|
||||
}
|
||||
|
||||
// ErrInvalidOutputFormat is called when the user provides format other than name/table
|
||||
type ErrInvalidOutputFormat struct {
|
||||
RequestedFormat string
|
||||
}
|
||||
|
||||
func (e ErrInvalidOutputFormat) Error() string {
|
||||
return fmt.Sprintf("invalid output format specified %s. Allowed values are table|name", e.RequestedFormat)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user