mirror of https://github.com/knative/client.git
Service describe: information about revision status, tags, latest [created, ready] revisions, and latest traffic (#379)
* Change symbols, add latest and latest ready * More info on what is latest and tags. Filter annotations and labels. * Make current tests pass * More tests * No longer require Annotations in test * Add error * Adjust tests to match adjusted output * Respond to review comments * one more * Fix double printing of revisions + test for it * Differentiate between latest traffic and latest ready w/o that being reason for traffic * Re-build with go 1.12 * Rebased * Limit non-verbose output to error and image * Fix tests for new format
This commit is contained in:
parent
e73a1c0348
commit
b27e366dcb
|
|
@ -4,10 +4,9 @@ Plugin command group
|
|||
|
||||
### Synopsis
|
||||
|
||||
Provides utilities for interacting and managing with `kn` plugins.
|
||||
|
||||
Plugins provide extended functionality that is not part of the core `kn` command-line distribution.
|
||||
Provides utilities for interacting and managing with kn plugins.
|
||||
|
||||
Plugins provide extended functionality that is not part of the core kn command-line distribution.
|
||||
Please refer to the documentation and examples for more information about how write your own plugins.
|
||||
|
||||
```
|
||||
|
|
@ -34,3 +33,4 @@ kn plugin [flags]
|
|||
|
||||
* [kn](kn.md) - Knative client
|
||||
* [kn plugin list](kn_plugin_list.md) - List plugins
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"knative.dev/serving/pkg/apis/autoscaling"
|
||||
"knative.dev/serving/pkg/apis/serving"
|
||||
|
|
@ -66,9 +67,12 @@ type revisionDesc struct {
|
|||
configurationGeneration int
|
||||
creationTimestamp time.Time
|
||||
|
||||
percent int
|
||||
latest *bool
|
||||
// traffic stuff
|
||||
percent int
|
||||
tag string
|
||||
latestTraffic *bool
|
||||
|
||||
// basic revision stuff
|
||||
logURL string
|
||||
timeoutSeconds *int64
|
||||
|
||||
|
|
@ -89,6 +93,12 @@ type revisionDesc struct {
|
|||
requestsCPU string
|
||||
limitsMemory string
|
||||
limitsCPU string
|
||||
|
||||
// status info
|
||||
ready corev1.ConditionStatus
|
||||
reason string
|
||||
latestCreated bool
|
||||
latestReady bool
|
||||
}
|
||||
|
||||
// [REMOVE COMMENT WHEN MOVING TO 0.7.0]
|
||||
|
|
@ -151,7 +161,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
return describe(cmd.OutOrStdout(), service, revisionDescs)
|
||||
return describe(cmd.OutOrStdout(), service, revisionDescs, printDetails)
|
||||
},
|
||||
}
|
||||
flags := command.Flags()
|
||||
|
|
@ -162,7 +172,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command {
|
|||
}
|
||||
|
||||
// Main action describing the service
|
||||
func describe(w io.Writer, service *v1alpha1.Service, revisions []*revisionDesc) error {
|
||||
func describe(w io.Writer, service *v1alpha1.Service, revisions []*revisionDesc, printDetails bool) error {
|
||||
dw := printers.NewPrefixWriter(w)
|
||||
|
||||
// Service info
|
||||
|
|
@ -173,7 +183,7 @@ func describe(w io.Writer, service *v1alpha1.Service, revisions []*revisionDesc)
|
|||
}
|
||||
|
||||
// Revisions summary info
|
||||
writeRevisions(dw, revisions)
|
||||
writeRevisions(dw, revisions, printDetails)
|
||||
dw.WriteLine()
|
||||
if err := dw.Flush(); err != nil {
|
||||
return err
|
||||
|
|
@ -205,29 +215,34 @@ func writeService(dw printers.PrefixWriter, service *v1alpha1.Service) {
|
|||
// Write out revisions associated with this service. By default only active
|
||||
// target revisions are printed, but with --all also inactive revisions
|
||||
// created by this services are shown
|
||||
func writeRevisions(dw printers.PrefixWriter, revisions []*revisionDesc) {
|
||||
func writeRevisions(dw printers.PrefixWriter, revisions []*revisionDesc, printDetails bool) {
|
||||
dw.WriteColsLn(printers.Level0, l("Revisions"))
|
||||
for _, revisionDesc := range revisions {
|
||||
dw.WriteColsLn(printers.Level1, formatPercentage(revisionDesc.percent), l("Name"), getRevisionNameWithGenerationAndAge(revisionDesc))
|
||||
dw.WriteColsLn(printers.Level1, formatBullet(revisionDesc.percent, revisionDesc.ready), revisionHeader(revisionDesc))
|
||||
if revisionDesc.ready == v1.ConditionFalse {
|
||||
dw.WriteColsLn(printers.Level1, "", l("Error"), revisionDesc.reason)
|
||||
}
|
||||
dw.WriteColsLn(printers.Level1, "", l("Image"), getImageDesc(revisionDesc))
|
||||
if revisionDesc.port != nil {
|
||||
dw.WriteColsLn(printers.Level1, "", l("Port"), strconv.FormatInt(int64(*revisionDesc.port), 10))
|
||||
}
|
||||
writeSliceDesc(dw, printers.Level1, revisionDesc.env, l("Env"), "\t")
|
||||
if printDetails {
|
||||
if revisionDesc.port != nil {
|
||||
dw.WriteColsLn(printers.Level1, "", l("Port"), strconv.FormatInt(int64(*revisionDesc.port), 10))
|
||||
}
|
||||
writeSliceDesc(dw, printers.Level1, revisionDesc.env, l("Env"), "\t")
|
||||
|
||||
// Scale spec if given
|
||||
if revisionDesc.maxScale != nil || revisionDesc.minScale != nil {
|
||||
dw.WriteColsLn(printers.Level1, "", l("Scale"), formatScale(revisionDesc.minScale, revisionDesc.maxScale))
|
||||
}
|
||||
// Scale spec if given
|
||||
if revisionDesc.maxScale != nil || revisionDesc.minScale != nil {
|
||||
dw.WriteColsLn(printers.Level1, "", l("Scale"), formatScale(revisionDesc.minScale, revisionDesc.maxScale))
|
||||
}
|
||||
|
||||
// Concurrency specs if given
|
||||
if revisionDesc.concurrencyLimit != nil || revisionDesc.concurrencyTarget != nil {
|
||||
writeConcurrencyOptions(dw, revisionDesc)
|
||||
}
|
||||
// Concurrency specs if given
|
||||
if revisionDesc.concurrencyLimit != nil || revisionDesc.concurrencyTarget != nil {
|
||||
writeConcurrencyOptions(dw, revisionDesc)
|
||||
}
|
||||
|
||||
// Resources if given
|
||||
writeResources(dw, "Memory", revisionDesc.requestsMemory, revisionDesc.limitsMemory)
|
||||
writeResources(dw, "CPU", revisionDesc.requestsCPU, revisionDesc.limitsCPU)
|
||||
// Resources if given
|
||||
writeResources(dw, "Memory", revisionDesc.requestsMemory, revisionDesc.limitsMemory)
|
||||
writeResources(dw, "CPU", revisionDesc.requestsCPU, revisionDesc.limitsCPU)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,12 +264,12 @@ func writeConditions(dw printers.PrefixWriter, service *v1alpha1.Service) {
|
|||
}
|
||||
|
||||
func writeConcurrencyOptions(dw printers.PrefixWriter, desc *revisionDesc) {
|
||||
dw.WriteColsLn(printers.Level1, "", l("Concurrency"))
|
||||
dw.WriteColsLn(printers.Level2, "", l("Concurrency"))
|
||||
if desc.concurrencyLimit != nil {
|
||||
dw.WriteColsLn(printers.Level2, "", "", l("Limit"), strconv.FormatInt(*desc.concurrencyLimit, 10))
|
||||
dw.WriteColsLn(printers.Level3, "", "", l("Limit"), strconv.FormatInt(*desc.concurrencyLimit, 10))
|
||||
}
|
||||
if desc.concurrencyTarget != nil {
|
||||
dw.WriteColsLn(printers.Level2, "", "", l("Target"), strconv.Itoa(*desc.concurrencyTarget))
|
||||
dw.WriteColsLn(printers.Level3, "", "", l("Target"), strconv.Itoa(*desc.concurrencyTarget))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,8 +299,19 @@ func formatScale(minScale *int, maxScale *int) string {
|
|||
}
|
||||
|
||||
// Format the revision name along with its generation. Use colors if enabled.
|
||||
func getRevisionNameWithGenerationAndAge(desc *revisionDesc) string {
|
||||
return desc.name + " " +
|
||||
func revisionHeader(desc *revisionDesc) string {
|
||||
header := desc.name
|
||||
if desc.latestTraffic != nil && *desc.latestTraffic {
|
||||
header = fmt.Sprintf("@latest (%s)", desc.name)
|
||||
} else if desc.latestReady {
|
||||
header = desc.name + " (current @latest)"
|
||||
} else if desc.latestCreated {
|
||||
header = desc.name + " (latest created)"
|
||||
}
|
||||
if desc.tag != "" {
|
||||
header = fmt.Sprintf("%s #%s", header, desc.tag)
|
||||
}
|
||||
return header + " " +
|
||||
"[" + strconv.Itoa(desc.configurationGeneration) + "]" +
|
||||
" " +
|
||||
"(" + age(desc.creationTimestamp) + ")"
|
||||
|
|
@ -314,9 +340,9 @@ func formatStatus(status corev1.ConditionStatus) string {
|
|||
case v1.ConditionTrue:
|
||||
return "++"
|
||||
case v1.ConditionFalse:
|
||||
return "--"
|
||||
return "!!"
|
||||
default:
|
||||
return ""
|
||||
return "??"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +351,7 @@ func getImageDesc(desc *revisionDesc) string {
|
|||
image := desc.image
|
||||
// Check if the user image is likely a more user-friendly description
|
||||
pinnedDesc := "at"
|
||||
if desc.userImage != "" && strings.Contains(image, "@") && desc.imageDigest != "" {
|
||||
if desc.userImage != "" && desc.imageDigest != "" {
|
||||
parts := strings.Split(image, "@")
|
||||
// Check if the user image refers to the same thing.
|
||||
if strings.HasPrefix(desc.userImage, parts[0]) {
|
||||
|
|
@ -345,11 +371,17 @@ func getImageDesc(desc *revisionDesc) string {
|
|||
func shortenDigest(digest string) string {
|
||||
match := imageDigestRegexp.FindStringSubmatch(digest)
|
||||
if len(match) > 1 {
|
||||
return string(match[1][:12])
|
||||
return string(match[1][:6])
|
||||
}
|
||||
return digest
|
||||
}
|
||||
|
||||
var boringDomains = map[string]bool{
|
||||
"serving.knative.dev": true,
|
||||
"client.knative.dev": true,
|
||||
"kubectl.kubernetes.io": true,
|
||||
}
|
||||
|
||||
// Write a map either compact in a single line (possibly truncated) or, if printDetails is set,
|
||||
// over multiple line, one line per key-value pair. The output is sorted by keys.
|
||||
func writeMapDesc(dw printers.PrefixWriter, indent int, m map[string]string, label string, labelPrefix string) {
|
||||
|
|
@ -359,7 +391,13 @@ func writeMapDesc(dw printers.PrefixWriter, indent int, m map[string]string, lab
|
|||
|
||||
var keys []string
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
parts := strings.Split(k, "/")
|
||||
if printDetails || len(parts) <= 1 || !boringDomains[parts[0]] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
|
|
@ -415,7 +453,7 @@ func writeResources(dw printers.PrefixWriter, label string, request string, limi
|
|||
return
|
||||
}
|
||||
|
||||
dw.WriteColsLn(printers.Level1, "", l(label), value)
|
||||
dw.WriteColsLn(printers.Level2, "", l(label), value)
|
||||
}
|
||||
|
||||
// Join to key=value pair, comma separated, and truncate if longer than a limit
|
||||
|
|
@ -436,11 +474,22 @@ func joinAndTruncate(sortedKeys []string, m map[string]string) string {
|
|||
}
|
||||
|
||||
// Format target percentage that it fits in the revision table
|
||||
func formatPercentage(percentage int) string {
|
||||
if percentage == 0 {
|
||||
return " -"
|
||||
func formatBullet(percentage int, status corev1.ConditionStatus) string {
|
||||
symbol := "+"
|
||||
switch status {
|
||||
case v1.ConditionTrue:
|
||||
if percentage > 0 {
|
||||
symbol = "%"
|
||||
}
|
||||
case v1.ConditionFalse:
|
||||
symbol = "!"
|
||||
default:
|
||||
symbol = "?"
|
||||
}
|
||||
return fmt.Sprintf("%3d%%", percentage)
|
||||
if percentage == 0 {
|
||||
return fmt.Sprintf(" %s", symbol)
|
||||
}
|
||||
return fmt.Sprintf("%3d%s", percentage, symbol)
|
||||
}
|
||||
|
||||
func age(t time.Time) string {
|
||||
|
|
@ -453,22 +502,28 @@ func age(t time.Time) string {
|
|||
// Call the backend to query revisions for the given service and build up
|
||||
// the view objects used for output
|
||||
func getRevisionDescriptions(client serving_kn_v1alpha1.KnServingClient, service *v1alpha1.Service, withDetails bool) ([]*revisionDesc, error) {
|
||||
revisionDescs := make(map[string]*revisionDesc)
|
||||
revisionsSeen := sets.NewString()
|
||||
revisionDescs := []*revisionDesc{}
|
||||
|
||||
trafficTargets := service.Status.Traffic
|
||||
|
||||
var err error
|
||||
for _, target := range trafficTargets {
|
||||
revision, err := extractRevisionFromTarget(client, target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot extract revision from service %s: %v", service.Name, err)
|
||||
}
|
||||
revisionDescs[revision.Name], err = newRevisionDesc(revision, &target)
|
||||
revisionsSeen.Insert(revision.Name)
|
||||
desc, err := newRevisionDesc(revision, &target, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
revisionDescs = append(revisionDescs, desc)
|
||||
}
|
||||
if revisionDescs, err = completeWithLatestRevisions(client, service, revisionsSeen, revisionDescs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if withDetails {
|
||||
if err := completeWithUntargetedRevisions(client, service, revisionDescs); err != nil {
|
||||
if revisionDescs, err = completeWithUntargetedRevisions(client, service, revisionsSeen, revisionDescs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -476,36 +531,53 @@ func getRevisionDescriptions(client serving_kn_v1alpha1.KnServingClient, service
|
|||
}
|
||||
|
||||
// Order the list of revisions so that the newest revisions are at the top
|
||||
func orderByConfigurationGeneration(descs map[string]*revisionDesc) []*revisionDesc {
|
||||
descsList := make([]*revisionDesc, len(descs))
|
||||
idx := 0
|
||||
for _, desc := range descs {
|
||||
descsList[idx] = desc
|
||||
idx++
|
||||
}
|
||||
sort.SliceStable(descsList, func(i, j int) bool {
|
||||
return descsList[i].configurationGeneration > descsList[j].configurationGeneration
|
||||
func orderByConfigurationGeneration(descs []*revisionDesc) []*revisionDesc {
|
||||
sort.SliceStable(descs, func(i, j int) bool {
|
||||
return descs[i].configurationGeneration > descs[j].configurationGeneration
|
||||
})
|
||||
return descsList
|
||||
return descs
|
||||
}
|
||||
|
||||
func completeWithUntargetedRevisions(client serving_kn_v1alpha1.KnServingClient, service *v1alpha1.Service, descs map[string]*revisionDesc) error {
|
||||
func completeWithLatestRevisions(client serving_kn_v1alpha1.KnServingClient, service *v1alpha1.Service, revisionsSeen sets.String, descs []*revisionDesc) ([]*revisionDesc, error) {
|
||||
for _, revisionName := range []string{service.Status.LatestCreatedRevisionName, service.Status.LatestReadyRevisionName} {
|
||||
if revisionsSeen.Has(revisionName) {
|
||||
continue
|
||||
}
|
||||
revisionsSeen.Insert(revisionName)
|
||||
rev, err := client.GetRevision(revisionName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newDesc, err := newRevisionDesc(rev, nil, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
descs = append(descs, newDesc)
|
||||
}
|
||||
return descs, nil
|
||||
}
|
||||
|
||||
func completeWithUntargetedRevisions(client serving_kn_v1alpha1.KnServingClient, service *v1alpha1.Service, revisionsSeen sets.String, descs []*revisionDesc) ([]*revisionDesc, error) {
|
||||
revisions, err := client.ListRevisions(serving_kn_v1alpha1.WithService(service.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
for _, revision := range revisions.Items {
|
||||
if _, ok := descs[revision.Name]; !ok {
|
||||
descs[revision.Name], err = newRevisionDesc(&revision, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if revisionsSeen.Has(revision.Name) {
|
||||
continue
|
||||
}
|
||||
revisionsSeen.Insert(revision.Name)
|
||||
newDesc, err := newRevisionDesc(&revision, nil, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
descs = append(descs, newDesc)
|
||||
|
||||
}
|
||||
return nil
|
||||
return descs, nil
|
||||
}
|
||||
|
||||
func newRevisionDesc(revision *v1alpha1.Revision, target *v1alpha1.TrafficTarget) (*revisionDesc, error) {
|
||||
func newRevisionDesc(revision *v1alpha1.Revision, target *v1alpha1.TrafficTarget, service *v1alpha1.Service) (*revisionDesc, error) {
|
||||
container := extractContainer(revision)
|
||||
generation, err := strconv.ParseInt(revision.Labels[serving.ConfigurationGenerationLabelKey], 0, 0)
|
||||
if err != nil {
|
||||
|
|
@ -521,8 +593,12 @@ func newRevisionDesc(revision *v1alpha1.Revision, target *v1alpha1.TrafficTarget
|
|||
|
||||
configurationGeneration: int(generation),
|
||||
configuration: revision.Labels[serving.ConfigurationLabelKey],
|
||||
|
||||
latestCreated: revision.Name == service.Status.LatestCreatedRevisionName,
|
||||
latestReady: revision.Name == service.Status.LatestReadyRevisionName,
|
||||
}
|
||||
|
||||
addStatusInfo(&revisionDesc, revision)
|
||||
addTargetInfo(&revisionDesc, target)
|
||||
addContainerInfo(&revisionDesc, container)
|
||||
addResourcesInfo(&revisionDesc, container)
|
||||
|
|
@ -533,10 +609,20 @@ func newRevisionDesc(revision *v1alpha1.Revision, target *v1alpha1.TrafficTarget
|
|||
return &revisionDesc, nil
|
||||
}
|
||||
|
||||
func addStatusInfo(desc *revisionDesc, revision *v1alpha1.Revision) {
|
||||
for _, condition := range revision.Status.Conditions {
|
||||
if condition.Type == "Ready" {
|
||||
desc.reason = condition.Reason
|
||||
desc.ready = condition.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addTargetInfo(desc *revisionDesc, target *v1alpha1.TrafficTarget) {
|
||||
if target != nil {
|
||||
desc.percent = target.Percent
|
||||
desc.latest = target.LatestRevision
|
||||
desc.latestTraffic = target.LatestRevision
|
||||
desc.tag = target.Tag
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import (
|
|||
client_serving "knative.dev/client/pkg/serving"
|
||||
knclient "knative.dev/client/pkg/serving/v1alpha1"
|
||||
"knative.dev/client/pkg/util"
|
||||
"knative.dev/pkg/ptr"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -63,14 +64,177 @@ func TestServiceDescribeBasic(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "Env:", "label1=lval1, label2=lval2\n"))
|
||||
assert.Assert(t, util.ContainsAll(output, "1234567"))
|
||||
assert.Assert(t, util.ContainsAll(output, "123456"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Annotations:", "anno1=aval1, anno2=aval2, anno3="))
|
||||
assert.Assert(t, cmp.Regexp(`(?m)\s*Annotations:.*\.\.\.$`, output))
|
||||
assert.Assert(t, util.ContainsAll(output, "Labels:", "label1=lval1, label2=lval2\n"))
|
||||
assert.Assert(t, util.ContainsAll(output, "[1]"))
|
||||
// no digest added (added only for details)
|
||||
assert.Assert(t, !strings.Contains(output, "(123456789012)"))
|
||||
|
||||
assert.Equal(t, strings.Count(output, "rev1"), 1)
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
r.Validate()
|
||||
}
|
||||
|
||||
func TestServiceDescribeSad(t *testing.T) {
|
||||
client := knclient.NewMockKnClient(t)
|
||||
r := client.Recorder()
|
||||
|
||||
expectedService := createTestService("foo", []string{"rev1"}, goodConditions())
|
||||
expectedService.Status.Conditions[0].Status = v1.ConditionFalse
|
||||
r.GetService("foo", &expectedService, nil)
|
||||
rev1 := createTestRevision("rev1", 1)
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
|
||||
output, err := executeServiceCommand(client, "describe", "foo")
|
||||
assert.NilError(t, err)
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "!!", "Ready"))
|
||||
|
||||
r.Validate()
|
||||
}
|
||||
|
||||
func TestServiceDescribeLatest(t *testing.T) {
|
||||
|
||||
// New mock client
|
||||
client := knclient.NewMockKnClient(t)
|
||||
r := client.Recorder()
|
||||
|
||||
expectedService := createTestService("foo", []string{"rev1"}, goodConditions())
|
||||
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true)
|
||||
|
||||
// Get service & revision
|
||||
r.GetService("foo", &expectedService, nil)
|
||||
rev1 := createTestRevision("rev1", 1)
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
|
||||
output, err := executeServiceCommand(client, "describe", "foo")
|
||||
assert.NilError(t, err)
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "@latest (rev1)"))
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
r.Validate()
|
||||
}
|
||||
|
||||
func TestServiceDescribeLatestNotInTraffic(t *testing.T) {
|
||||
|
||||
// New mock client
|
||||
client := knclient.NewMockKnClient(t)
|
||||
|
||||
// Recording:
|
||||
r := client.Recorder()
|
||||
// Prepare service
|
||||
expectedService := createTestService("foo", []string{"rev1", "rev2"}, goodConditions())
|
||||
expectedService.Status.Traffic = expectedService.Status.Traffic[:1]
|
||||
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(false)
|
||||
expectedService.Status.Traffic[0].Percent = 100
|
||||
|
||||
// Get service & revision
|
||||
r.GetService("foo", &expectedService, nil)
|
||||
rev1 := createTestRevision("rev1", 1)
|
||||
rev2 := createTestRevision("rev2", 2)
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
r.GetRevision("rev2", &rev2, nil)
|
||||
|
||||
// Testing:
|
||||
output, err := executeServiceCommand(client, "describe", "foo")
|
||||
assert.NilError(t, err)
|
||||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "rev2 (current @latest)"))
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
r.Validate()
|
||||
}
|
||||
|
||||
func TestServiceDescribeEachNamedOnce(t *testing.T) {
|
||||
|
||||
// New mock client
|
||||
client := knclient.NewMockKnClient(t)
|
||||
|
||||
// Recording:
|
||||
r := client.Recorder()
|
||||
// Prepare service
|
||||
expectedService := createTestService("foo", []string{"rev1", "rev2"}, goodConditions())
|
||||
expectedService.Status.Traffic = expectedService.Status.Traffic[:1]
|
||||
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(false)
|
||||
expectedService.Status.Traffic[0].Percent = 100
|
||||
|
||||
// Get service & revision
|
||||
r.GetService("foo", &expectedService, nil)
|
||||
rev1 := createTestRevision("rev1", 1)
|
||||
rev2 := createTestRevision("rev2", 2)
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
r.GetRevision("rev2", &rev2, nil)
|
||||
|
||||
// Testing:
|
||||
output, err := executeServiceCommand(client, "describe", "foo")
|
||||
assert.NilError(t, err)
|
||||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "rev1", "rev2"))
|
||||
assert.Equal(t, strings.Count(output, "rev2"), 1)
|
||||
assert.Equal(t, strings.Count(output, "rev1"), 1)
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
r.Validate()
|
||||
}
|
||||
|
||||
func TestServiceDescribeLatestAndCurrentBothHaveTrafficEntries(t *testing.T) {
|
||||
// New mock client
|
||||
client := knclient.NewMockKnClient(t)
|
||||
|
||||
// Recording:
|
||||
r := client.Recorder()
|
||||
// Prepare service
|
||||
expectedService := createTestService("foo", []string{"rev1", "rev1"}, goodConditions())
|
||||
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true)
|
||||
expectedService.Status.Traffic[0].Tag = "latest"
|
||||
expectedService.Status.Traffic[1].Tag = "current"
|
||||
|
||||
// Get service & revision
|
||||
r.GetService("foo", &expectedService, nil)
|
||||
rev1 := createTestRevision("rev1", 1)
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
|
||||
// Testing:
|
||||
output, err := executeServiceCommand(client, "describe", "foo")
|
||||
assert.NilError(t, err)
|
||||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "@latest (rev1) #latest", "rev1 (current @latest) #current", "50%"))
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
r.Validate()
|
||||
}
|
||||
|
||||
func TestServiceDescribeLatestCreatedIsBroken(t *testing.T) {
|
||||
// New mock client
|
||||
client := knclient.NewMockKnClient(t)
|
||||
|
||||
// Recording:
|
||||
r := client.Recorder()
|
||||
// Prepare service
|
||||
expectedService := createTestService("foo", []string{"rev1"}, goodConditions())
|
||||
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true)
|
||||
expectedService.Status.LatestCreatedRevisionName = "rev2"
|
||||
|
||||
// Get service & revision
|
||||
r.GetService("foo", &expectedService, nil)
|
||||
rev1 := createTestRevision("rev1", 1)
|
||||
rev2 := createTestRevision("rev2", 2)
|
||||
rev2.Status.Conditions[0].Status = v1.ConditionFalse
|
||||
r.GetRevision("rev1", &rev1, nil)
|
||||
r.GetRevision("rev2", &rev2, nil)
|
||||
|
||||
// Testing:
|
||||
output, err := executeServiceCommand(client, "describe", "foo")
|
||||
assert.NilError(t, err)
|
||||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
assert.Assert(t, util.ContainsAll(output, "!", "rev2", "100%", "@latest (rev1)"))
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
r.Validate()
|
||||
|
|
@ -238,8 +402,8 @@ func TestServiceDescribeUserImageVsImage(t *testing.T) {
|
|||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
|
||||
assert.Assert(t, util.ContainsAll(output, "Image", "Name", "gcr.io/test/image:latest (at 123456789012)",
|
||||
"gcr.io/test/image:latest (pinned to 123456789012)", "gcr.io/a/b (at 123456789012)", "gcr.io/x/y"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Image", "Name",
|
||||
"gcr.io/test/image:latest (pinned to 123456)", "gcr.io/a/b (at 123456)", "gcr.io/x/y"))
|
||||
assert.Assert(t, util.ContainsAll(output, "[1]", "[2]"))
|
||||
|
||||
// Validate that all recorded API methods have been called
|
||||
|
|
@ -285,7 +449,7 @@ func TestServiceDescribeVerbose(t *testing.T) {
|
|||
|
||||
validateServiceOutput(t, "foo", output)
|
||||
|
||||
assert.Assert(t, util.ContainsAll(output, "Image", "Name", "gcr.io/test/image (at 123456789012)", "50%", "(0s)"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Image", "Name", "gcr.io/test/image (at 123456)", "50%", "(0s)"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Env:", "label1=lval1\n", "label2=lval2\n"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Annotations:", "anno1=aval1\n", "anno2=aval2\n"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Labels:", "label1=lval1\n", "label2=lval2\n"))
|
||||
|
|
@ -327,7 +491,7 @@ func validateServiceOutput(t *testing.T, service string, output string) {
|
|||
assert.Assert(t, cmp.Regexp("Address:\\s+http://"+service+".default.svc.cluster.local", output))
|
||||
assert.Assert(t, cmp.Regexp("URL:\\s+"+service+".default.example.com", output))
|
||||
|
||||
assert.Assert(t, util.ContainsAll(output, "Age:", "Revisions:", "Conditions:", "Labels:", "Annotations:", "Port:", "8080"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Age:", "Revisions:", "Conditions:", "Labels:", "Annotations:"))
|
||||
assert.Assert(t, util.ContainsAll(output, "Ready", "RoutesReady", "OK", "TYPE", "AGE", "REASON"))
|
||||
}
|
||||
|
||||
|
|
@ -363,6 +527,9 @@ func createTestService(name string, revisionNames []string, conditions duckv1bet
|
|||
},
|
||||
},
|
||||
}
|
||||
service.Status.LatestCreatedRevisionName = revisionNames[len(revisionNames)-1]
|
||||
service.Status.LatestReadyRevisionName = revisionNames[len(revisionNames)-1]
|
||||
|
||||
if len(revisionNames) > 0 {
|
||||
trafficTargets := make([]v1alpha1.TrafficTarget, 0)
|
||||
for _, rname := range revisionNames {
|
||||
|
|
@ -455,6 +622,9 @@ func createTestRevision(revision string, gen int64) v1alpha1.Revision {
|
|||
},
|
||||
Status: v1alpha1.RevisionStatus{
|
||||
ImageDigest: "gcr.io/test/image@" + imageDigest,
|
||||
Status: duckv1beta1.Status{
|
||||
Conditions: goodConditions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func (test *e2eTest) serviceDescribe(t *testing.T, serviceName string) {
|
|||
|
||||
assert.Assert(t, util.ContainsAll(out, serviceName, test.kn.namespace, KnDefaultTestImage))
|
||||
assert.Assert(t, util.ContainsAll(out, "Conditions", "ConfigurationsReady", "Ready", "RoutesReady"))
|
||||
assert.Assert(t, util.ContainsAll(out, "Name", "Namespace", "URL", "Address", "Annotations", "Age", "Revisions"))
|
||||
assert.Assert(t, util.ContainsAll(out, "Name", "Namespace", "URL", "Address", "Age", "Revisions"))
|
||||
}
|
||||
|
||||
func (test *e2eTest) serviceUpdate(t *testing.T, serviceName string, args []string) {
|
||||
|
|
|
|||
|
|
@ -186,9 +186,9 @@ k8s.io/apimachinery/pkg/util/duration
|
|||
k8s.io/apimachinery/pkg/apis/meta/v1beta1
|
||||
k8s.io/apimachinery/pkg/runtime
|
||||
k8s.io/apimachinery/pkg/api/resource
|
||||
k8s.io/apimachinery/pkg/util/sets
|
||||
k8s.io/apimachinery/pkg/api/meta
|
||||
k8s.io/apimachinery/pkg/util/runtime
|
||||
k8s.io/apimachinery/pkg/util/sets
|
||||
k8s.io/apimachinery/pkg/fields
|
||||
k8s.io/apimachinery/pkg/labels
|
||||
k8s.io/apimachinery/pkg/runtime/schema
|
||||
|
|
|
|||
Loading…
Reference in New Issue