Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds more columns in revision get command output #70

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
294a884
Adds more columns in service list command output
navidshaikh Apr 4, 2019
2827b7d
Adds AGE column in serivce list command output
navidshaikh Apr 5, 2019
4a4a96f
Updates TestListEmpty test for expected output
navidshaikh Apr 5, 2019
d15a64f
Updates unit tests for service list command
navidshaikh Apr 5, 2019
9d98a55
Adds vendor/k8s.io/apimachinery/pkg/util/duration/duration.go
navidshaikh Apr 9, 2019
0a6ae83
Returns nil after printing the service list table
navidshaikh Apr 10, 2019
0e9011d
Uses camel case variable names
navidshaikh Apr 15, 2019
da886c8
Removes unused variables
navidshaikh Apr 15, 2019
8973a25
Updates the method name and uses defer to flush the printer
navidshaikh Apr 15, 2019
e2996e0
Moves calculate age utility from util/printers to util/
navidshaikh Apr 15, 2019
4524a7d
Adds a switch to enable generic output format flags
navidshaikh Apr 16, 2019
71c75c9
Adds test for jsonpath output flag
navidshaikh Apr 16, 2019
9743273
Renames service list command to service get
navidshaikh Apr 24, 2019
5ecb716
Fixes tests
navidshaikh Apr 24, 2019
c9b0e56
Updates the tests
navidshaikh May 1, 2019
7d9f459
Adds more columns in revision list command output
navidshaikh Apr 10, 2019
dcd5536
Adds unit tests for revision list command
navidshaikh Apr 12, 2019
b1f8a1b
Uses defer for flushing the printer
navidshaikh Apr 15, 2019
c09e603
Uses NewTabWriter instead of GetNewTabWriter
navidshaikh Apr 15, 2019
cf6a788
Uses camel casing for var names
navidshaikh Apr 24, 2019
8ed576b
Renames revision list command to revision get
navidshaikh Apr 24, 2019
63d387a
Restores multiple output format flags from genericclioptions
navidshaikh Apr 24, 2019
849a71b
Uses the constants from serving package
navidshaikh Apr 24, 2019
c4a66e3
Updates tests to for multiple routes to single revision
navidshaikh Apr 24, 2019
8a1bf15
Adds conditions and ready column
navidshaikh May 1, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/kn/commands/revision.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
156 changes: 156 additions & 0 deletions pkg/kn/commands/revision_get.go
Original file line number Diff line number Diff line change
@@ -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"
}
179 changes: 179 additions & 0 deletions pkg/kn/commands/revision_get_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading