diff --git a/go.mod b/go.mod index 04561e2d5a..e5454aeb30 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/json-iterator/go v1.1.6 // indirect github.com/knative/build v0.5.0 // indirect - github.com/knative/pkg v0.0.0-20190329155329-916205998db9 // indirect + github.com/knative/pkg v0.0.0-20190329155329-916205998db9 github.com/knative/serving v0.5.2 github.com/knative/test-infra v0.0.0-20190404172656-4ce16d390c55 github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect diff --git a/pkg/kn/commands/revision.go b/pkg/kn/commands/revision.go index 78e7da225c..d4746165d6 100644 --- a/pkg/kn/commands/revision.go +++ b/pkg/kn/commands/revision.go @@ -23,7 +23,7 @@ func NewRevisionCommand(p *KnParams) *cobra.Command { Use: "revision", Short: "Revision command group", } - revisionCmd.AddCommand(NewRevisionListCommand(p)) + revisionCmd.AddCommand(NewRevisionGetCommand(p)) revisionCmd.AddCommand(NewRevisionDescribeCommand(p)) return revisionCmd } diff --git a/pkg/kn/commands/revision_get.go b/pkg/kn/commands/revision_get.go new file mode 100644 index 0000000000..6938125aec --- /dev/null +++ b/pkg/kn/commands/revision_get.go @@ -0,0 +1,156 @@ +// Copyright © 2018 The Knative Authors +// +// 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 +// +// http://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 commands + +import ( + "fmt" + "strings" + "text/tabwriter" + + util "github.com/knative/client/pkg/util" + printers "github.com/knative/client/pkg/util/printers" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + serving "github.com/knative/serving/pkg/apis/serving" + v1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +// NewRevisionGetCommand represent the 'revision get' command +func NewRevisionGetCommand(p *KnParams) *cobra.Command { + + revisionGetPrintFlags := genericclioptions.NewPrintFlags("") + + RevisionGetCmd := &cobra.Command{ + Use: "get", + Short: "Get available revisions.", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := p.ServingFactory() + if err != nil { + return err + } + namespace, err := GetNamespace(cmd) + if err != nil { + return err + } + revisions, err := client.Revisions(namespace).List(v1.ListOptions{}) + if err != nil { + return err + } + + routes, err := client.Routes(namespace).List(v1.ListOptions{}) + if err != nil { + return err + } + + // if output format flag is set, delegate the printing to cli-runtime + if cmd.Flag("output").Changed { + printer, err := revisionGetPrintFlags.ToPrinter() + if err != nil { + return err + } + revisions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: "knative.dev", + Version: "v1alpha1", + Kind: "Revision"}) + err = printer.PrintObj(revisions, cmd.OutOrStdout()) + if err != nil { + return err + } + return nil + } + // if no output format flag is set, lets print the human readable outp + printer := printers.NewTabWriter(cmd.OutOrStdout()) + // make sure the printer is flushed to stdout before returning + defer printer.Flush() + + if err := printRevisionList(printer, *revisions, *routes); err != nil { + return err + } + return nil + }, + } + AddNamespaceFlags(RevisionGetCmd.Flags(), true) + revisionGetPrintFlags.AddFlags(RevisionGetCmd) + return RevisionGetCmd +} + +// printRevisionList takes care of printing revisions +func printRevisionList( + printer *tabwriter.Writer, + revisions v1alpha1.RevisionList, + routes v1alpha1.RouteList) error { + // case where no revisions are present + if len(revisions.Items) < 1 { + fmt.Fprintln(printer, "No resources found.") + return nil + } + columnNames := []string{"NAME", "SERVICE", "AGE", "CONDITIONS", "READY", "TRAFFIC"} + if _, err := fmt.Fprintf(printer, "%s\n", strings.Join(columnNames, "\t")); err != nil { + return err + } + for _, rev := range revisions.Items { + row := []string{ + rev.Name, + rev.Labels[serving.ConfigurationLabelKey], + util.CalculateAge(rev.CreationTimestamp.Time), + ConditionsValue(rev.Status.Conditions), + ReadyCondition(rev.Status.Conditions), + // RouteTrafficValue returns comma separated traffic string + RouteTrafficValue(rev, routes.Items), + } + if _, err := fmt.Fprintf(printer, "%s\n", strings.Join(row, "\t")); err != nil { + return err + } + } + return nil +} + +// RouteTrafficValue returns a string with comma separated traffic for revision +func RouteTrafficValue(rev v1alpha1.Revision, routes []v1alpha1.Route) string { + var traffic []string + for _, route := range routes { + for _, target := range route.Status.Traffic { + if target.RevisionName == rev.Name { + traffic = append(traffic, fmt.Sprintf("%d%% -> %s", target.Percent, route.Status.Domain)) + } + } + } + return strings.Join(traffic, " ") +} + +// ConditionsValue returns the True conditions count among total conditions +func ConditionsValue(conditions duckv1alpha1.Conditions) string { + var total, ok int + for _, condition := range conditions { + total++ + if condition.Status == "True" { + ok++ + } + } + return fmt.Sprintf("%d OK / %d", ok, total) +} + +// ReadyCondition returns status of resource's Ready type condition +func ReadyCondition(conditions duckv1alpha1.Conditions) string { + for _, condition := range conditions { + if condition.Type == duckv1alpha1.ConditionReady { + return string(condition.Status) + } + } + return "Unknown" +} diff --git a/pkg/kn/commands/revision_get_test.go b/pkg/kn/commands/revision_get_test.go new file mode 100644 index 0000000000..9b6e190ed6 --- /dev/null +++ b/pkg/kn/commands/revision_get_test.go @@ -0,0 +1,179 @@ +// Copyright © 2018 The Knative Authors +// +// 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 +// +// http://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 commands + +import ( + "bytes" + "strings" + "testing" + + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + serving "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" + "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + client_testing "k8s.io/client-go/testing" +) + +func fakeRevisionGet(args []string, revisions *v1alpha1.RevisionList, routes *v1alpha1.RouteList) ( + action client_testing.Action, output []string, err error) { + buf := new(bytes.Buffer) + fakeServing := &fake.FakeServingV1alpha1{&client_testing.Fake{}} + cmd := NewKnCommand(KnParams{ + Output: buf, + ServingFactory: func() (serving.ServingV1alpha1Interface, error) { return fakeServing, nil }, + }) + fakeServing.AddReactor("*", "*", + func(a client_testing.Action) (bool, runtime.Object, error) { + action = a + if action.Matches("list", "routes") { + return true, routes, nil + } + return true, revisions, nil + }) + + cmd.SetArgs(args) + err = cmd.Execute() + if err != nil { + return + } + output = strings.Split(buf.String(), "\n") + return +} + +func TestRevisionListEmpty(t *testing.T) { + action, output, err := fakeRevisionGet( + []string{"revision", "get"}, + &v1alpha1.RevisionList{}, + &v1alpha1.RouteList{}) + + if err != nil { + t.Error(err) + return + } + expected := []string{"No resources found.", ""} + for i, s := range output { + if s != expected[i] { + t.Errorf("%d Bad output line %v", i, s) + } + } + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "routes") { + t.Errorf("Bad action %v", action) + } +} + +var revisionType = metav1.TypeMeta{ + Kind: "revision", + APIVersion: "serving.knative.dev/v1alpha1", +} +var routeType = metav1.TypeMeta{ + Kind: "route", + APIVersion: "serving.knative.dev/v1alpha1", +} + +func TestRevisionGetDefaultOutput(t *testing.T) { + + // sample RevisionList + rev_list := &v1alpha1.RevisionList{ + Items: []v1alpha1.Revision{ + v1alpha1.Revision{ + TypeMeta: revisionType, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + v1alpha1.Revision{ + TypeMeta: revisionType, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + }, + }, + } + // sample RouteList + route_list := &v1alpha1.RouteList{ + Items: []v1alpha1.Route{ + v1alpha1.Route{ + TypeMeta: routeType, + Status: v1alpha1.RouteStatus{ + RouteStatusFields: v1alpha1.RouteStatusFields{ + Domain: "foo.default.example.com", + Traffic: []v1alpha1.TrafficTarget{ + v1alpha1.TrafficTarget{ + RevisionName: "foo", + Percent: 100, + }, + }, + }, + }, + }, + v1alpha1.Route{ + TypeMeta: routeType, + Status: v1alpha1.RouteStatus{ + RouteStatusFields: v1alpha1.RouteStatusFields{ + Domain: "bar.default.example.com", + Traffic: []v1alpha1.TrafficTarget{ + v1alpha1.TrafficTarget{ + RevisionName: "bar", + Percent: 100, + }, + }, + }, + }, + }, + v1alpha1.Route{ + TypeMeta: routeType, + Status: v1alpha1.RouteStatus{ + RouteStatusFields: v1alpha1.RouteStatusFields{ + Domain: "baz.default.example.com", + Traffic: []v1alpha1.TrafficTarget{ + v1alpha1.TrafficTarget{ + RevisionName: "bar", + Percent: 100, + }, + }, + }, + }, + }, + }, + } + + action, output, err := fakeRevisionGet( + []string{"revision", "get"}, + rev_list, + route_list) + if err != nil { + t.Fatal(err) + } + // each line's tab/spaces are replaced by comma + expected := []string{"NAME,SERVICE,AGE,CONDITIONS,READY,TRAFFIC", + "foo,,,0 OK / 0,Unknown,100% -> foo.default.example.com", + // test multiple routes to single revision + "bar,,,0 OK / 0,Unknown,100% -> bar.default.example.com 100% -> baz.default.example.com"} + expected_lines := strings.Split(tabbedOutput(expected), "\n") + + for i, s := range output { + if s != expected_lines[i] { + t.Errorf("Bad output line %v expected %v", s, expected_lines[i]) + } + } + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "routes") { + t.Errorf("Bad action %v", action) + } +} diff --git a/pkg/kn/commands/revision_list.go b/pkg/kn/commands/revision_list.go deleted file mode 100644 index a3f41af8b6..0000000000 --- a/pkg/kn/commands/revision_list.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2018 The Knative Authors -// -// 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 -// -// http://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 commands - -import ( - "github.com/spf13/cobra" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/genericclioptions" -) - -var revisionListPrintFlags *genericclioptions.PrintFlags - -// listCmd represents the list command -func NewRevisionListCommand(p *KnParams) *cobra.Command { - revisionListPrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput( - "jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}") - revisionListCmd := &cobra.Command{ - Use: "list", - Short: "List available revisions.", - RunE: func(cmd *cobra.Command, args []string) error { - client, err := p.ServingFactory() - if err != nil { - return err - } - namespace, err := GetNamespace(cmd) - if err != nil { - return err - } - revision, err := client.Revisions(namespace).List(v1.ListOptions{}) - if err != nil { - return err - } - - printer, err := revisionListPrintFlags.ToPrinter() - if err != nil { - return err - } - revision.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ - Group: "knative.dev", - Version: "v1alpha1", - Kind: "Revision"}) - err = printer.PrintObj(revision, cmd.OutOrStdout()) - if err != nil { - return err - } - return nil - }, - } - AddNamespaceFlags(revisionListCmd.Flags(), true) - revisionListPrintFlags.AddFlags(revisionListCmd) - return revisionListCmd -} diff --git a/pkg/kn/commands/service.go b/pkg/kn/commands/service.go index 77441e92a6..392c250679 100644 --- a/pkg/kn/commands/service.go +++ b/pkg/kn/commands/service.go @@ -23,7 +23,7 @@ func NewServiceCommand(p *KnParams) *cobra.Command { Use: "service", Short: "Service command group", } - serviceCmd.AddCommand(NewServiceListCommand(p)) + serviceCmd.AddCommand(NewServiceGetCommand(p)) serviceCmd.AddCommand(NewServiceDescribeCommand(p)) serviceCmd.AddCommand(NewServiceCreateCommand(p)) serviceCmd.AddCommand(NewServiceDeleteCommand(p)) diff --git a/pkg/kn/commands/service_get.go b/pkg/kn/commands/service_get.go new file mode 100644 index 0000000000..ae6d55195e --- /dev/null +++ b/pkg/kn/commands/service_get.go @@ -0,0 +1,105 @@ +// Copyright © 2018 The Knative Authors +// +// 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 +// +// http://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 commands + +import ( + "fmt" + "strings" + "text/tabwriter" + + util "github.com/knative/client/pkg/util" + printers "github.com/knative/client/pkg/util/printers" + servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +// NewServiceGetCommand represents the get command +func NewServiceGetCommand(p *KnParams) *cobra.Command { + + serviceGetPrintFlags := genericclioptions.NewPrintFlags("") + + ServiceGetCommand := &cobra.Command{ + Use: "get", + Short: "Get available services.", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := p.ServingFactory() + if err != nil { + return err + } + namespace, err := GetNamespace(cmd) + if err != nil { + return err + } + service, err := client.Services(namespace).List(v1.ListOptions{}) + if err != nil { + return err + } + // if output format flag is set, delegate the printing to cli-runtime + if cmd.Flag("output").Changed { + printer, err := serviceGetPrintFlags.ToPrinter() + if err != nil { + return err + } + service.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: "knative.dev", + Version: "v1alpha1", + Kind: "Service"}) + err = printer.PrintObj(service, cmd.OutOrStdout()) + if err != nil { + return err + } + return nil + } + // if no output format flag is set, lets print the human readable output + printer := printers.NewTabWriter(cmd.OutOrStdout()) + // make sure the printer is flushed to stdout before returning + defer printer.Flush() + + if err := printServiceList(printer, *service); err != nil { + return err + } + return nil + }, + } + AddNamespaceFlags(ServiceGetCommand.Flags(), true) + serviceGetPrintFlags.AddFlags(ServiceGetCommand) + return ServiceGetCommand +} + +// printServiceList prints human readable list of services installed, +// if requested specific output format, its delegated to cli-runtime +func printServiceList(printer *tabwriter.Writer, services servingv1alpha1.ServiceList) error { + // case where no services are present + if len(services.Items) < 1 { + fmt.Fprintln(printer, "No resources found.") + return nil + } + columnNames := []string{"NAME", "DOMAIN", "LATESTCREATED", "LATESTREADY", "AGE"} + if _, err := fmt.Fprintf(printer, "%s\n", strings.Join(columnNames, "\t")); err != nil { + return err + } + for _, ksvc := range services.Items { + _, err := fmt.Fprintf(printer, "%s\n", strings.Join([]string{ksvc.Name, ksvc.Status.Domain, + ksvc.Status.LatestCreatedRevisionName, ksvc.Status.LatestReadyRevisionName, + util.CalculateAge(ksvc.CreationTimestamp.Time)}, "\t")) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/kn/commands/service_get_test.go b/pkg/kn/commands/service_get_test.go new file mode 100644 index 0000000000..4e6450dd50 --- /dev/null +++ b/pkg/kn/commands/service_get_test.go @@ -0,0 +1,177 @@ +// Copyright © 2018 The Knative Authors +// +// 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 +// +// http://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 commands + +import ( + "bytes" + "fmt" + "strings" + "testing" + + printers "github.com/knative/client/pkg/util/printers" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + serving "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" + "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + client_testing "k8s.io/client-go/testing" +) + +func fakeServiceGet(args []string, response *v1alpha1.ServiceList) (action client_testing.Action, output []string, err error) { + buf := new(bytes.Buffer) + fakeServing := &fake.FakeServingV1alpha1{&client_testing.Fake{}} + cmd := NewKnCommand(KnParams{ + Output: buf, + ServingFactory: func() (serving.ServingV1alpha1Interface, error) { return fakeServing, nil }, + }) + fakeServing.AddReactor("*", "*", + func(a client_testing.Action) (bool, runtime.Object, error) { + action = a + return true, response, nil + }) + cmd.SetArgs(args) + err = cmd.Execute() + if err != nil { + return + } + output = strings.Split(buf.String(), "\n") + return +} + +func TestListEmpty(t *testing.T) { + action, output, err := fakeServiceGet([]string{"service", "get"}, &v1alpha1.ServiceList{}) + if err != nil { + t.Error(err) + return + } + expected := []string{"No resources found.", ""} + for i, s := range output { + if s != expected[i] { + t.Errorf("%d Bad output line %v", i, s) + } + } + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "services") { + t.Errorf("Bad action %v", action) + } +} + +var serviceType = metav1.TypeMeta{ + Kind: "service", + APIVersion: "serving.knative.dev/v1alpha1", +} + +// tabbedOutput takes a list of strings and returns the tabwriter formatted output +func tabbedOutput(s []string) string { + buf := new(bytes.Buffer) + printer := printers.NewTabWriter(buf) + + for _, line := range s { + lineItems := strings.Split(line, ",") + fmt.Fprintf(printer, "%s\n", strings.Join(lineItems, "\t")) + } + printer.Flush() + return buf.String() + +} +func TestGetDefaultOutput(t *testing.T) { + action, output, err := fakeServiceGet([]string{"service", "get"}, &v1alpha1.ServiceList{ + Items: []v1alpha1.Service{ + v1alpha1.Service{ + TypeMeta: serviceType, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: v1alpha1.ServiceStatus{ + RouteStatusFields: v1alpha1.RouteStatusFields{ + Domain: "foo.default.example.com", + }, + ConfigurationStatusFields: v1alpha1.ConfigurationStatusFields{ + LatestCreatedRevisionName: "foo-abcde", + LatestReadyRevisionName: "foo-abcde", + }, + }, + }, + v1alpha1.Service{ + TypeMeta: serviceType, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + Status: v1alpha1.ServiceStatus{ + RouteStatusFields: v1alpha1.RouteStatusFields{ + Domain: "bar.default.example.com", + }, + ConfigurationStatusFields: v1alpha1.ConfigurationStatusFields{ + LatestCreatedRevisionName: "bar-abcde", + LatestReadyRevisionName: "bar-abcde", + }, + }, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + // each line's tab/spaces are replaced by comma + expected := []string{"NAME,DOMAIN,LATESTCREATED,LATESTREADY,AGE", + "foo,foo.default.example.com,foo-abcde,foo-abcde,", + "bar,bar.default.example.com,bar-abcde,bar-abcde,"} + expectedLines := strings.Split(tabbedOutput(expected), "\n") + + for i, s := range output { + if s != expectedLines[i] { + t.Errorf("Bad output line %v expected %v", s, expectedLines[i]) + } + } + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "services") { + t.Errorf("Bad action %v", action) + } +} + +func TestOutputWithJsonpath(t *testing.T) { + action, output, err := fakeServiceGet([]string{"service", "get", "-o", "jsonpath={range .items[*]}{.metadata.name} {end}"}, &v1alpha1.ServiceList{ + Items: []v1alpha1.Service{ + v1alpha1.Service{ + TypeMeta: serviceType, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + v1alpha1.Service{ + TypeMeta: serviceType, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + expectedLines := []string{"foo bar ", ""} + for i, s := range output { + if s != expectedLines[i] { + t.Errorf("Bad output line %v expected %v", s, expectedLines[i]) + } + } + if action == nil { + t.Errorf("No action") + } else if !action.Matches("list", "services") { + t.Errorf("Bad action %v", action) + } +} diff --git a/pkg/kn/commands/service_list.go b/pkg/kn/commands/service_list.go deleted file mode 100644 index a8e0696ae7..0000000000 --- a/pkg/kn/commands/service_list.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2018 The Knative Authors -// -// 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 -// -// http://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 commands - -import ( - "github.com/spf13/cobra" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/genericclioptions" -) - -var serviceListPrintFlags *genericclioptions.PrintFlags - -// listCmd represents the list command -func NewServiceListCommand(p *KnParams) *cobra.Command { - serviceListPrintFlags := genericclioptions.NewPrintFlags("").WithDefaultOutput( - "jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}") - serviceListCommand := &cobra.Command{ - Use: "list", - Short: "List available services.", - RunE: func(cmd *cobra.Command, args []string) error { - client, err := p.ServingFactory() - if err != nil { - return err - } - namespace, err := GetNamespace(cmd) - if err != nil { - return err - } - service, err := client.Services(namespace).List(v1.ListOptions{}) - if err != nil { - return err - } - - printer, err := serviceListPrintFlags.ToPrinter() - if err != nil { - return err - } - service.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ - Group: "knative.dev", - Version: "v1alpha1", - Kind: "Service"}) - err = printer.PrintObj(service, cmd.OutOrStdout()) - if err != nil { - return err - } - return nil - }, - } - AddNamespaceFlags(serviceListCommand.Flags(), true) - serviceListPrintFlags.AddFlags(serviceListCommand) - return serviceListCommand -} diff --git a/pkg/kn/commands/service_list_test.go b/pkg/kn/commands/service_list_test.go deleted file mode 100644 index c34acc9ced..0000000000 --- a/pkg/kn/commands/service_list_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright © 2018 The Knative Authors -// -// 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 -// -// http://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 commands - -import ( - "bytes" - "strings" - "testing" - - "github.com/knative/serving/pkg/apis/serving/v1alpha1" - serving "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" - "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - client_testing "k8s.io/client-go/testing" -) - -func fakeList(args []string, response *v1alpha1.ServiceList) (action client_testing.Action, output []string, err error) { - buf := new(bytes.Buffer) - fakeServing := &fake.FakeServingV1alpha1{&client_testing.Fake{}} - cmd := NewKnCommand(KnParams{ - Output: buf, - ServingFactory: func() (serving.ServingV1alpha1Interface, error) { return fakeServing, nil }, - }) - fakeServing.AddReactor("*", "*", - func(a client_testing.Action) (bool, runtime.Object, error) { - action = a - return true, response, nil - }) - cmd.SetArgs(args) - err = cmd.Execute() - if err != nil { - return - } - output = strings.Split(buf.String(), "\n") - return -} - -func TestListEmpty(t *testing.T) { - action, output, err := fakeList([]string{"service", "list"}, &v1alpha1.ServiceList{}) - if err != nil { - t.Error(err) - return - } - for _, s := range output { - if s != "" { - t.Errorf("Bad output line %v", s) - } - } - if action == nil { - t.Errorf("No action") - } else if !action.Matches("list", "services") { - t.Errorf("Bad action %v", action) - } -} - -var serviceType = metav1.TypeMeta{ - Kind: "service", - APIVersion: "serving.knative.dev/v1alpha1", -} - -func TestListDefaultOutput(t *testing.T) { - action, output, err := fakeList([]string{"service", "list"}, &v1alpha1.ServiceList{ - Items: []v1alpha1.Service{ - v1alpha1.Service{ - TypeMeta: serviceType, - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, - v1alpha1.Service{ - TypeMeta: serviceType, - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - }, - }, - }, - }) - if err != nil { - t.Fatal(err) - } - expected := []string{"foo", "bar", ""} - for i, s := range output { - if s != expected[i] { - t.Errorf("Bad output line %v expected %v", s, expected[i]) - } - } - if action == nil { - t.Errorf("No action") - } else if !action.Matches("list", "services") { - t.Errorf("Bad action %v", action) - } -} diff --git a/pkg/util/age.go b/pkg/util/age.go new file mode 100644 index 0000000000..eddd1e4ebb --- /dev/null +++ b/pkg/util/age.go @@ -0,0 +1,30 @@ +// Copyright © 2018 The Knative Authors +// +// 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 +// +// http://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 implie +// See the License for the specific language governing permissions and +// limitations under the License. + +package printers + +import ( + "time" + + "k8s.io/apimachinery/pkg/util/duration" +) + +// CalculateAge calculates age of given resource using CreationTimestamp +// and current time. +func CalculateAge(t time.Time) string { + if t.IsZero() { + return "" + } + return duration.ShortHumanDuration(time.Now().Sub(t)) +} diff --git a/pkg/util/printers/tabwriter.go b/pkg/util/printers/tabwriter.go new file mode 100644 index 0000000000..89dd1d789d --- /dev/null +++ b/pkg/util/printers/tabwriter.go @@ -0,0 +1,34 @@ +// Copyright © 2018 The Knative Authors +// +// 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 +// +// http://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 implie +// See the License for the specific language governing permissions and +// limitations under the License. + +package printers + +import ( + "io" + + "text/tabwriter" +) + +const ( + tabwriterMinWidth = 6 + tabwriterWidth = 4 + tabwriterPadding = 3 + tabwriterPadChar = ' ' + tabwriterFlags = tabwriter.TabIndent +) + +// NewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text. +func NewTabWriter(output io.Writer) *tabwriter.Writer { + return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags) +} diff --git a/vendor/k8s.io/apimachinery/pkg/util/duration/duration.go b/vendor/k8s.io/apimachinery/pkg/util/duration/duration.go new file mode 100644 index 0000000000..961ec5ed8b --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/duration/duration.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 + + http://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 duration + +import ( + "fmt" + "time" +) + +// ShortHumanDuration returns a succint representation of the provided duration +// with limited precision for consumption by humans. +func ShortHumanDuration(d time.Duration) string { + // Allow deviation no more than 2 seconds(excluded) to tolerate machine time + // inconsistence, it can be considered as almost now. + if seconds := int(d.Seconds()); seconds < -1 { + return fmt.Sprintf("") + } else if seconds < 0 { + return fmt.Sprintf("0s") + } else if seconds < 60 { + return fmt.Sprintf("%ds", seconds) + } else if minutes := int(d.Minutes()); minutes < 60 { + return fmt.Sprintf("%dm", minutes) + } else if hours := int(d.Hours()); hours < 24 { + return fmt.Sprintf("%dh", hours) + } else if hours < 24*365 { + return fmt.Sprintf("%dd", hours/24) + } + return fmt.Sprintf("%dy", int(d.Hours()/24/365)) +} + +// HumanDuration returns a succint representation of the provided duration +// with limited precision for consumption by humans. It provides ~2-3 significant +// figures of duration. +func HumanDuration(d time.Duration) string { + // Allow deviation no more than 2 seconds(excluded) to tolerate machine time + // inconsistence, it can be considered as almost now. + if seconds := int(d.Seconds()); seconds < -1 { + return fmt.Sprintf("") + } else if seconds < 0 { + return fmt.Sprintf("0s") + } else if seconds < 60*2 { + return fmt.Sprintf("%ds", seconds) + } + minutes := int(d / time.Minute) + if minutes < 10 { + s := int(d/time.Second) % 60 + if s == 0 { + return fmt.Sprintf("%dm", minutes) + } + return fmt.Sprintf("%dm%ds", minutes, s) + } else if minutes < 60*3 { + return fmt.Sprintf("%dm", minutes) + } + hours := int(d / time.Hour) + if hours < 8 { + m := int(d/time.Minute) % 60 + if m == 0 { + return fmt.Sprintf("%dh", hours) + } + return fmt.Sprintf("%dh%dm", hours, m) + } else if hours < 48 { + return fmt.Sprintf("%dh", hours) + } else if hours < 24*8 { + h := hours % 24 + if h == 0 { + return fmt.Sprintf("%dd", hours/24) + } + return fmt.Sprintf("%dd%dh", hours/24, h) + } else if hours < 24*365*2 { + return fmt.Sprintf("%dd", hours/24) + } else if hours < 24*365*8 { + return fmt.Sprintf("%dy%dd", hours/24/365, (hours/24)%365) + } + return fmt.Sprintf("%dy", int(hours/24/365)) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4915cc317e..34c828b25c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -61,20 +61,20 @@ github.com/json-iterator/go github.com/knative/build/pkg/apis/build/v1alpha1 github.com/knative/build/pkg/apis/build # github.com/knative/pkg v0.0.0-20190329155329-916205998db9 -github.com/knative/pkg/apis github.com/knative/pkg/apis/duck/v1alpha1 +github.com/knative/pkg/apis +github.com/knative/pkg/apis/duck github.com/knative/pkg/kmeta github.com/knative/pkg/kmp -github.com/knative/pkg/apis/duck github.com/knative/pkg/configmap # github.com/knative/serving v0.5.2 +github.com/knative/serving/pkg/apis/serving github.com/knative/serving/pkg/apis/serving/v1alpha1 github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1 github.com/knative/serving/pkg/apis/autoscaling github.com/knative/serving/pkg/apis/config github.com/knative/serving/pkg/apis/networking github.com/knative/serving/pkg/apis/networking/v1alpha1 -github.com/knative/serving/pkg/apis/serving github.com/knative/serving/pkg/client/clientset/versioned/scheme github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake github.com/knative/serving/pkg/apis/autoscaling/v1alpha1 @@ -191,10 +191,11 @@ k8s.io/api/storage/v1beta1 k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/apimachinery/pkg/runtime/schema +k8s.io/apimachinery/pkg/util/duration +k8s.io/apimachinery/pkg/runtime k8s.io/apimachinery/pkg/api/equality k8s.io/apimachinery/pkg/api/validation k8s.io/apimachinery/pkg/apis/meta/v1/unstructured -k8s.io/apimachinery/pkg/runtime k8s.io/apimachinery/pkg/util/intstr k8s.io/apimachinery/pkg/util/sets k8s.io/apimachinery/pkg/util/validation @@ -211,10 +212,10 @@ k8s.io/apimachinery/pkg/util/json k8s.io/apimachinery/pkg/util/net k8s.io/apimachinery/pkg/util/yaml k8s.io/apimachinery/pkg/util/errors -k8s.io/apimachinery/pkg/apis/meta/v1/validation -k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/pkg/conversion/queryparams k8s.io/apimachinery/pkg/util/naming +k8s.io/apimachinery/pkg/apis/meta/v1/validation +k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/pkg/runtime/serializer/json k8s.io/apimachinery/pkg/runtime/serializer/protobuf k8s.io/apimachinery/pkg/runtime/serializer/recognizer @@ -251,6 +252,7 @@ k8s.io/client-go/util/jsonpath k8s.io/client-go/tools/auth k8s.io/client-go/tools/clientcmd/api/latest k8s.io/client-go/testing +k8s.io/client-go/dynamic k8s.io/client-go/tools/cache k8s.io/client-go/pkg/version k8s.io/client-go/plugin/pkg/client/auth/exec @@ -262,7 +264,6 @@ k8s.io/client-go/util/flowcontrol k8s.io/client-go/kubernetes/scheme k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/tools/clientcmd/api/v1 -k8s.io/client-go/dynamic k8s.io/client-go/tools/pager k8s.io/client-go/util/buffer k8s.io/client-go/util/retry