Adds service get command (#90)

* Adds service get command

 replaces service list command

* Updates go.mod and vendors/*

* Adds message if no services found in requested namespace

* Adds tests for service get
This commit is contained in:
Navid Shaikh 2019-05-13 23:26:40 +05:30 committed by Knative Prow Robot
parent 6582e8239a
commit bd7d57fa09
8 changed files with 402 additions and 62 deletions

2
go.mod
View File

@ -16,7 +16,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-20190509163238-a721698dbe49
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect

View File

@ -0,0 +1,54 @@
// Copyright © 2019 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 (
hprinters "github.com/knative/client/pkg/printers"
"github.com/spf13/cobra"
)
// HumanPrintFlags provides default flags necessary for printing.
// Given the following flag values, a printer can be requested that knows
// how to handle printing based on these values.
type HumanPrintFlags struct {
//TODO: Add more flags as required
}
// AllowedFormats returns more customized formating options
func (f *HumanPrintFlags) AllowedFormats() []string {
// TODO: Add more formats eg: wide
return []string{""}
}
// ToPrinter receives returns a printer capable of
// handling human-readable output.
func (f *HumanPrintFlags) ToPrinter() (hprinters.ResourcePrinter, error) {
p := hprinters.NewTablePrinter(hprinters.PrintOptions{})
// Add the column definitions and respective functions
ServiceGetHandlers(p)
return p, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to human-readable printing to it
func (f *HumanPrintFlags) AddFlags(c *cobra.Command) {
//TODO: Add more flags as required
}
// NewHumanPrintFlags returns flags associated with
// human-readable printing, with default values set.
func NewHumanPrintFlags() *HumanPrintFlags {
return &HumanPrintFlags{}
}

View File

@ -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))

View File

@ -1,4 +1,4 @@
// Copyright © 2018 The Knative Authors
// Copyright © 2019 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.
@ -15,21 +15,20 @@
package commands
import (
"fmt"
"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
// NewServiceGetCommand represents 'kn service get' command
func NewServiceGetCommand(p *KnParams) *cobra.Command {
serviceGetFlags := NewServiceGetFlags()
// 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.",
serviceGetCommand := &cobra.Command{
Use: "get",
Short: "Get available services.",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := p.ServingFactory()
if err != nil {
@ -43,15 +42,20 @@ func NewServiceListCommand(p *KnParams) *cobra.Command {
if err != nil {
return err
}
printer, err := serviceListPrintFlags.ToPrinter()
if err != nil {
return err
if len(service.Items) == 0 {
fmt.Fprintf(cmd.OutOrStdout(), "No resources found.\n")
return nil
}
service.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
Group: "knative.dev",
Version: "v1alpha1",
Kind: "Service"})
printer, err := serviceGetFlags.ToPrinter()
if err != nil {
return err
}
err = printer.PrintObj(service, cmd.OutOrStdout())
if err != nil {
return err
@ -59,7 +63,7 @@ func NewServiceListCommand(p *KnParams) *cobra.Command {
return nil
},
}
AddNamespaceFlags(serviceListCommand.Flags(), true)
serviceListPrintFlags.AddFlags(serviceListCommand)
return serviceListCommand
AddNamespaceFlags(serviceGetCommand.Flags(), true)
serviceGetFlags.AddFlags(serviceGetCommand)
return serviceGetCommand
}

View File

@ -0,0 +1,181 @@
// Copyright © 2019 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 im
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
hprinters "github.com/knative/client/pkg/printers"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/cli-runtime/pkg/genericclioptions"
"time"
)
// ServiceGetFlags composes common printer flag structs
// used in the Get command.
type ServiceGetFlags struct {
GenericPrintFlags *genericclioptions.PrintFlags
HumanReadableFlags *HumanPrintFlags
}
// AllowedFormats is the list of formats in which data can be displayed
func (f *ServiceGetFlags) AllowedFormats() []string {
formats := f.GenericPrintFlags.AllowedFormats()
formats = append(formats, f.HumanReadableFlags.AllowedFormats()...)
return formats
}
// ToPrinter attempts to find a composed set of ServiceGetFlags suitable for
// returning a printer based on current flag values.
func (f *ServiceGetFlags) ToPrinter() (hprinters.ResourcePrinter, error) {
// if there are flags specified for generic printing
if f.GenericPrintFlags.OutputFlagSpecified() {
p, err := f.GenericPrintFlags.ToPrinter()
if err != nil {
return nil, err
}
return p, nil
}
// if no flags specified, use the table printing
p, err := f.HumanReadableFlags.ToPrinter()
if err != nil {
return nil, err
}
return p, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to humanreadable and template printing.
func (f *ServiceGetFlags) AddFlags(cmd *cobra.Command) {
f.GenericPrintFlags.AddFlags(cmd)
f.HumanReadableFlags.AddFlags(cmd)
}
// NewGetPrintFlags returns flags associated with humanreadable,
// template, and "name" printing, with default values set.
func NewServiceGetFlags() *ServiceGetFlags {
return &ServiceGetFlags{
GenericPrintFlags: genericclioptions.NewPrintFlags(""),
HumanReadableFlags: NewHumanPrintFlags(),
}
}
// ServiceGetHandlers adds print handlers for service get command
func ServiceGetHandlers(h hprinters.PrintHandler) {
kServiceColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Description: "Name of the knative service."},
{Name: "Domain", Type: "string", Description: "Domain name of the knative service."},
//{Name: "LastCreatedRevision", Type: "string", Description: "Name of last revision created."},
//{Name: "LastReadyRevision", Type: "string", Description: "Name of last ready revision."},
{Name: "Generation", Type: "integer", Description: "Sequence number of 'Generation' of the service that was last processed by the controller."},
{Name: "Age", Type: "string", Description: "Age of the service."},
{Name: "Conditions", Type: "string", Description: "Conditions describing statuses of service components."},
{Name: "Ready", Type: "string", Description: "Ready condition status of the service."},
{Name: "Reason", Type: "string", Description: "Reason for non-ready condition of the service."},
}
h.TableHandler(kServiceColumnDefinitions, printKService)
h.TableHandler(kServiceColumnDefinitions, printKServiceList)
}
// conditionsValue returns the True conditions count among total conditions
func conditionsValue(conditions duckv1alpha1.Conditions) string {
var ok int
for _, condition := range conditions {
if condition.Status == "True" {
ok++
}
}
return fmt.Sprintf("%d OK / %d", ok, len(conditions))
}
// 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>"
}
func nonReadyConditionReason(conditions duckv1alpha1.Conditions) string {
for _, condition := range conditions {
if condition.Type == duckv1alpha1.ConditionReady {
if string(condition.Status) == "True" {
return ""
}
if condition.Message != "" {
return fmt.Sprintf("%s : %s", condition.Reason, condition.Message)
}
return string(condition.Reason)
}
}
return "<unknown>"
}
// translateTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestampSince(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.HumanDuration(time.Since(timestamp.Time))
}
// printKServiceList populates the knative service list table rows
func printKServiceList(kServiceList *servingv1alpha1.ServiceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(kServiceList.Items))
for _, ksvc := range kServiceList.Items {
r, err := printKService(&ksvc, options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
// printKService populates the knative service table rows
func printKService(kService *servingv1alpha1.Service, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
name := kService.Name
domain := kService.Status.Domain
//lastCreatedRevision := kService.Status.LatestCreatedRevisionName
//lastReadyRevision := kService.Status.LatestReadyRevisionName
generation := kService.Status.ObservedGeneration
age := translateTimestampSince(kService.CreationTimestamp)
conditions := conditionsValue(kService.Status.Conditions)
ready := readyCondition(kService.Status.Conditions)
reason := nonReadyConditionReason(kService.Status.Conditions)
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: kService},
}
row.Cells = append(row.Cells,
name,
domain,
//lastCreatedRevision,
//lastReadyRevision,
generation,
age,
conditions,
ready,
reason)
return []metav1beta1.TableRow{row}, nil
}

View File

@ -19,6 +19,8 @@ import (
"strings"
"testing"
//servinglib "github.com/knative/client/pkg/serving"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"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"
@ -27,7 +29,7 @@ import (
client_testing "k8s.io/client-go/testing"
)
func fakeList(args []string, response *v1alpha1.ServiceList) (action client_testing.Action, output []string, err error) {
func fakeGet(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{
@ -48,58 +50,67 @@ func fakeList(args []string, response *v1alpha1.ServiceList) (action client_test
return
}
func TestListEmpty(t *testing.T) {
action, output, err := fakeList([]string{"service", "list"}, &v1alpha1.ServiceList{})
func TestGetEmpty(t *testing.T) {
action, output, err := fakeGet([]string{"service", "get"}, &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)
} else if output[0] != "No resources found." {
t.Errorf("Bad output %s", output[0])
}
}
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",
},
},
},
})
service1 := createMockServiceWithParams(t, "foo", "foo.default.example.com", 1)
service2 := createMockServiceWithParams(t, "bar", "bar.default.example.com", 2)
serviceList := &v1alpha1.ServiceList{Items: []v1alpha1.Service{*service1, *service2}}
action, output, err := fakeGet([]string{"service", "get"}, serviceList)
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)
}
testContains(t, output[0], []string{"NAME", "DOMAIN", "GENERATION", "AGE", "CONDITIONS", "READY", "REASON"}, "column header")
testContains(t, output[1], []string{"foo", "foo.default.example.com", "1"}, "value")
testContains(t, output[2], []string{"bar", "bar.default.example.com", "2"}, "value")
}
func testContains(t *testing.T, output string, sub []string, element string) {
for _, each := range sub {
if !strings.Contains(output, each) {
t.Errorf("Missing %s: %s", element, each)
}
}
}
func createMockServiceWithParams(t *testing.T, name, domain string, generation int64) *v1alpha1.Service {
service := &v1alpha1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "knative.dev/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
RunLatest: &v1alpha1.RunLatestType{},
},
Status: v1alpha1.ServiceStatus{
Status: duckv1alpha1.Status{
ObservedGeneration: generation},
RouteStatusFields: v1alpha1.RouteStatusFields{
Domain: domain,
},
},
}
return service
}

View File

@ -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("<invalid>")
} 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("<invalid>")
} 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))
}

17
vendor/modules.txt vendored
View File

@ -63,11 +63,11 @@ 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/v1alpha1
@ -195,10 +195,11 @@ k8s.io/api/storage/v1beta1
# k8s.io/apimachinery v0.0.0-20190221084156-01f179d85dbc
k8s.io/apimachinery/pkg/api/resource
k8s.io/apimachinery/pkg/apis/meta/v1
k8s.io/apimachinery/pkg/runtime/schema
k8s.io/apimachinery/pkg/api/meta
k8s.io/apimachinery/pkg/apis/meta/v1beta1
k8s.io/apimachinery/pkg/runtime
k8s.io/apimachinery/pkg/runtime/schema
k8s.io/apimachinery/pkg/util/duration
k8s.io/apimachinery/pkg/api/meta
k8s.io/apimachinery/pkg/util/runtime
k8s.io/apimachinery/pkg/api/equality
k8s.io/apimachinery/pkg/api/validation
@ -213,12 +214,12 @@ k8s.io/apimachinery/pkg/conversion
k8s.io/apimachinery/pkg/fields
k8s.io/apimachinery/pkg/labels
k8s.io/apimachinery/pkg/selection
k8s.io/apimachinery/pkg/conversion/queryparams
k8s.io/apimachinery/pkg/util/errors
k8s.io/apimachinery/pkg/util/json
k8s.io/apimachinery/pkg/util/naming
k8s.io/apimachinery/pkg/util/net
k8s.io/apimachinery/pkg/util/yaml
k8s.io/apimachinery/pkg/util/errors
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
@ -256,6 +257,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
@ -267,7 +269,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