diff --git a/pkg/eks/eks.go b/pkg/eks/eks.go index 42b9f322e3..04fc08d424 100644 --- a/pkg/eks/eks.go +++ b/pkg/eks/eks.go @@ -127,7 +127,7 @@ func (c *ClusterProvider) doListClusters(chunkSize int64, printer printers.Outpu } } - printer.PrintObj(allClusterNames, os.Stdout) + printer.PrintObj("clusters", allClusterNames, os.Stdout) return nil } @@ -141,9 +141,11 @@ func (c *ClusterProvider) doGetCluster(clusterName *string, printer printers.Out return errors.Wrapf(err, "unable to describe control plane %q", *clusterName) } logger.Debug("cluster = %#v", output) + + clusters := []*awseks.Cluster{output.Cluster} // TODO: in the future this will have multiple clusters + printer.PrintObj("clusters", clusters, os.Stdout) + if *output.Cluster.Status == awseks.ClusterStatusActive { - clusters := []*awseks.Cluster{output.Cluster} // TODO: in the future this will have multiple clusters - printer.PrintObj(clusters, os.Stdout) if logger.Level >= 4 { stacks, err := c.ListReadyStacks(fmt.Sprintf("^EKS-%s-.*$", *clusterName)) diff --git a/pkg/eks/eks_test.go b/pkg/eks/eks_test.go index 09fb31d061..d9eff39175 100644 --- a/pkg/eks/eks_test.go +++ b/pkg/eks/eks_test.go @@ -1,8 +1,10 @@ package eks_test import ( + "bytes" "fmt" - "time" + "io/ioutil" + "os" "github.com/aws/aws-sdk-go/aws" cfn "github.com/aws/aws-sdk-go/service/cloudformation" @@ -31,12 +33,10 @@ var _ = Describe("Eks", func() { var ( clusterName string err error - created time.Time ) BeforeEach(func() { clusterName = "test-cluster" - created = time.Now() p = testutils.NewMockProvider() @@ -50,16 +50,7 @@ var _ = Describe("Eks", func() { p.MockEKS().On("DescribeCluster", mock.MatchedBy(func(input *awseks.DescribeClusterInput) bool { return *input.Name == clusterName })).Return(&awseks.DescribeClusterOutput{ - Cluster: &awseks.Cluster{ - Name: aws.String(clusterName), - Status: aws.String(awseks.ClusterStatusActive), - Arn: aws.String("arn-12345678"), - CreatedAt: &created, - ResourcesVpcConfig: &awseks.VpcConfigResponse{ - VpcId: aws.String("vpc-1234"), - SubnetIds: []*string{aws.String("sub1"), aws.String("sub2")}, - }, - }, + Cluster: testutils.NewFakeCluster(clusterName, awseks.ClusterStatusActive), }, nil) }) @@ -117,6 +108,79 @@ var _ = Describe("Eks", func() { }) }) + Context("with a cluster name but cluster isn't ready", func() { + var ( + clusterName string + err error + originalStdout *os.File + reader *os.File + writer *os.File + ) + + BeforeEach(func() { + originalStdout = os.Stdout + reader, writer, _ = os.Pipe() + os.Stdout = writer + + clusterName = "test-cluster" + logger.Level = 1 + + p = testutils.NewMockProvider() + + c = &ClusterProvider{ + Spec: &ClusterConfig{ + ClusterName: clusterName, + }, + Provider: p, + } + + p.MockEKS().On("DescribeCluster", mock.MatchedBy(func(input *awseks.DescribeClusterInput) bool { + return *input.Name == clusterName + })).Return(&awseks.DescribeClusterOutput{ + Cluster: testutils.NewFakeCluster(clusterName, awseks.ClusterStatusDeleting), + }, nil) + }) + + JustBeforeEach(func() { + err = c.ListClusters(100, output) + }) + + AfterEach(func() { + os.Stdout = originalStdout + }) + + It("should not error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + It("should have called AWS EKS service once", func() { + Expect(p.MockEKS().AssertNumberOfCalls(GinkgoT(), "DescribeCluster", 1)).To(BeTrue()) + }) + + It("should not call AWS CFN ListStackPages", func() { + Expect(p.MockCloudFormation().AssertNumberOfCalls(GinkgoT(), "ListStacksPages", 0)).To(BeTrue()) + }) + + It("the output should equal the golden file singlecluster_deleting.golden", func() { + writer.Close() + g, err := ioutil.ReadFile("testdata/singlecluster_deleting.golden") + if err != nil { + GinkgoT().Fatalf("failed reading .golden: %s", err) + } + + actualOutput, _ := ioutil.ReadAll(reader) + + bytesAreEqual := bytes.Equal(actualOutput, g) + + if !bytesAreEqual { + fmt.Printf("\nActual:\n%s\n", string(actualOutput)) + fmt.Printf("Expected:\n%s\n", string(g)) + } + + Expect(bytesAreEqual).To(BeTrue()) + }) + }) + Context("with no cluster name", func() { var ( err error diff --git a/pkg/eks/testdata/singlecluster_deleting.golden b/pkg/eks/testdata/singlecluster_deleting.golden new file mode 100644 index 0000000000..d579573ffb --- /dev/null +++ b/pkg/eks/testdata/singlecluster_deleting.golden @@ -0,0 +1,21 @@ +[ + { + "Arn": "arn-12345678", + "CertificateAuthority": null, + "ClientRequestToken": null, + "CreatedAt": "0001-01-01T00:00:00Z", + "Endpoint": null, + "Name": "test-cluster", + "ResourcesVpcConfig": { + "SecurityGroupIds": null, + "SubnetIds": [ + "sub1", + "sub2" + ], + "VpcId": "vpc-1234" + }, + "RoleArn": null, + "Status": "DELETING", + "Version": null + } +] \ No newline at end of file diff --git a/pkg/printers/json.go b/pkg/printers/json.go index 9412002c07..a46b68c99b 100644 --- a/pkg/printers/json.go +++ b/pkg/printers/json.go @@ -25,7 +25,7 @@ func NewJSONPrinter() OutputPrinter { // PrintObj will print the passed object formatted as JSON to // the supplied writer. -func (j *JSONPrinter) PrintObj(obj interface{}, writer io.Writer) error { +func (j *JSONPrinter) PrintObj(kind string, obj interface{}, writer io.Writer) error { b, err := json.MarshalIndent(obj, j.prefix, j.indent) if err != nil { return err diff --git a/pkg/printers/json_test.go b/pkg/printers/json_test.go index 098762214f..f739525c08 100644 --- a/pkg/printers/json_test.go +++ b/pkg/printers/json_test.go @@ -57,7 +57,7 @@ var _ = Describe("JSON Printer", func() { JustBeforeEach(func() { w := bufio.NewWriter(&actualBytes) - err = printer.PrintObj([]*awseks.Cluster{cluster}, w) + err = printer.PrintObj("clusters", []*awseks.Cluster{cluster}, w) w.Flush() }) @@ -121,7 +121,7 @@ var _ = Describe("JSON Printer", func() { JustBeforeEach(func() { w := bufio.NewWriter(&actualBytes) - err = printer.PrintObj(clusters, w) + err = printer.PrintObj("clusters", clusters, w) w.Flush() }) diff --git a/pkg/printers/printers.go b/pkg/printers/printers.go index afb3ce9814..6ee11bc121 100644 --- a/pkg/printers/printers.go +++ b/pkg/printers/printers.go @@ -5,10 +5,14 @@ import ( "io" ) +// OutputPrinter is the interface that printer must implement. This allows +// new printers to be added in the future. type OutputPrinter interface { - PrintObj(interface{}, io.Writer) error + PrintObj(kind string, obj interface{}, writer io.Writer) error } +// NewPrinter creates a new printer based in the printer type requested +// as a string. func NewPrinter(printerType string) (OutputPrinter, error) { var printer OutputPrinter diff --git a/pkg/printers/table.go b/pkg/printers/table.go index 2da2d00505..3ad444331c 100644 --- a/pkg/printers/table.go +++ b/pkg/printers/table.go @@ -1,8 +1,13 @@ package printers import ( + "bufio" + "fmt" "io" + "reflect" + "strings" + "github.com/pkg/errors" "k8s.io/kops/util/pkg/tables" ) @@ -20,10 +25,23 @@ func NewTablePrinter() OutputPrinter { // PrintObj will print the passed object formatted as textual // table to the supplied writer. -func (t *TablePrinter) PrintObj(obj interface{}, writer io.Writer) error { +func (t *TablePrinter) PrintObj(kind string, obj interface{}, writer io.Writer) error { + itemsValue := reflect.ValueOf(obj) + if itemsValue.Kind() != reflect.Slice { + return errors.Errorf("table printer expects a slice but the kind was %v", itemsValue.Kind()) + } + + if itemsValue.Len() == 0 { + w := bufio.NewWriter(writer) + w.WriteString(fmt.Sprintf("No %s found\n", strings.ToLower(kind))) + w.Flush() + return nil + } + return t.table.Render(obj, writer, t.columnames...) } +// AddColumn adds a column to the table that will be printed func (t *TablePrinter) AddColumn(name string, getter interface{}) { t.columnames = append(t.columnames, name) t.table.AddColumn(name, getter) diff --git a/pkg/printers/table_test.go b/pkg/printers/table_test.go index 7a477ad138..daea128154 100644 --- a/pkg/printers/table_test.go +++ b/pkg/printers/table_test.go @@ -41,7 +41,7 @@ var _ = Describe("Table Printer", func() { _ = printer.(*TablePrinter) }) - Context("given a cluster struct and calling PrintObj", func() { + Context("given just a cluster struct (no slice) and calling PrintObj", func() { var ( cluster *awseks.Cluster err error @@ -64,7 +64,80 @@ var _ = Describe("Table Printer", func() { JustBeforeEach(func() { w := bufio.NewWriter(&actualBytes) - err = printer.PrintObj([]*awseks.Cluster{cluster}, w) + err = printer.PrintObj("clusters", cluster, w) + w.Flush() + }) + + AfterEach(func() { + actualBytes.Reset() + }) + + It("should have returned an error", func() { + Expect(err).To(HaveOccurred()) + }) + }) + + Context("given an empty slice with no cluster structs and calling PrintObj", func() { + var ( + err error + actualBytes bytes.Buffer + ) + + JustBeforeEach(func() { + w := bufio.NewWriter(&actualBytes) + err = printer.PrintObj("clusters", []*awseks.Cluster{}, w) + w.Flush() + }) + + AfterEach(func() { + actualBytes.Reset() + }) + + It("should not error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + It("the output should equal the golden file tabletest_emptyslicegolden", func() { + g, err := ioutil.ReadFile("testdata/tabletest_emptyslice.golden") + if err != nil { + GinkgoT().Fatalf("failed reading .golden: %s", err) + } + + bytesAreEqual := bytes.Equal(actualBytes.Bytes(), g) + + if !bytesAreEqual { + fmt.Printf("\nActual:\n%s\n", string(actualBytes.Bytes())) + fmt.Printf("Expected:\n%s\n", string(g)) + } + + Expect(bytesAreEqual).To(BeTrue()) + }) + }) + + Context("given a slice with a cluster struct and calling PrintObj", func() { + var ( + cluster *awseks.Cluster + err error + actualBytes bytes.Buffer + ) + + BeforeEach(func() { + created := &time.Time{} + cluster = &awseks.Cluster{ + Name: aws.String("test-cluster"), + Status: aws.String(awseks.ClusterStatusActive), + Arn: aws.String("arn-12345678"), + CreatedAt: created, + ResourcesVpcConfig: &awseks.VpcConfigResponse{ + VpcId: aws.String("vpc-1234"), + SubnetIds: []*string{aws.String("sub1"), aws.String("sub2")}, + }, + } + }) + + JustBeforeEach(func() { + w := bufio.NewWriter(&actualBytes) + err = printer.PrintObj("clusters", []*awseks.Cluster{cluster}, w) w.Flush() }) @@ -93,7 +166,7 @@ var _ = Describe("Table Printer", func() { }) }) - Context("given 2 cluster structs and calling PrintObj", func() { + Context("given a slice with 2 cluster structs and calling PrintObj", func() { var ( clusters []*awseks.Cluster err error @@ -128,7 +201,7 @@ var _ = Describe("Table Printer", func() { JustBeforeEach(func() { w := bufio.NewWriter(&actualBytes) - err = printer.PrintObj(clusters, w) + err = printer.PrintObj("clusters", clusters, w) w.Flush() }) diff --git a/pkg/printers/testdata/tabletest_emptyslice.golden b/pkg/printers/testdata/tabletest_emptyslice.golden new file mode 100644 index 0000000000..32d49f89e9 --- /dev/null +++ b/pkg/printers/testdata/tabletest_emptyslice.golden @@ -0,0 +1 @@ +No clusters found diff --git a/pkg/printers/yaml.go b/pkg/printers/yaml.go index 9024c2bcce..49dfe1043d 100644 --- a/pkg/printers/yaml.go +++ b/pkg/printers/yaml.go @@ -17,7 +17,7 @@ func NewYAMLPrinter() OutputPrinter { // PrintObj will print the passed object formatted as YAML to // the supplied writer. -func (j *YAMLPrinter) PrintObj(obj interface{}, writer io.Writer) error { +func (j *YAMLPrinter) PrintObj(kind string, obj interface{}, writer io.Writer) error { b, err := yaml.Marshal(obj) if err != nil { return err diff --git a/pkg/printers/yaml_test.go b/pkg/printers/yaml_test.go index 682ec38eb8..5a215a3a64 100644 --- a/pkg/printers/yaml_test.go +++ b/pkg/printers/yaml_test.go @@ -60,7 +60,7 @@ var _ = Describe("YAML Printer", func() { JustBeforeEach(func() { w := bufio.NewWriter(&actualBytes) - err = printer.PrintObj([]*awseks.Cluster{cluster}, w) + err = printer.PrintObj("clusters", []*awseks.Cluster{cluster}, w) w.Flush() }) @@ -124,7 +124,7 @@ var _ = Describe("YAML Printer", func() { JustBeforeEach(func() { w := bufio.NewWriter(&actualBytes) - err = printer.PrintObj(clusters, w) + err = printer.PrintObj("clusters", clusters, w) w.Flush() }) diff --git a/pkg/testutils/cluster.go b/pkg/testutils/cluster.go new file mode 100644 index 0000000000..fc6f4744a9 --- /dev/null +++ b/pkg/testutils/cluster.go @@ -0,0 +1,26 @@ +package testutils + +import ( + "time" + + "github.com/aws/aws-sdk-go/aws" + awseks "github.com/aws/aws-sdk-go/service/eks" +) + +// NewFakeCluster creates a new fake cluster to be used in the tests +func NewFakeCluster(clusterName string, status string) *awseks.Cluster { + created := &time.Time{} + + cluster := &awseks.Cluster{ + Name: aws.String(clusterName), + Status: aws.String(status), + Arn: aws.String("arn-12345678"), + CreatedAt: created, + ResourcesVpcConfig: &awseks.VpcConfigResponse{ + VpcId: aws.String("vpc-1234"), + SubnetIds: []*string{aws.String("sub1"), aws.String("sub2")}, + }, + } + + return cluster +}