diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 2c855345a143..5749a42cb5a9 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -323,6 +323,7 @@ spec: # One of "runLatest", "release", "pinned" (DEPRECATED), or "manual" release: # Ordered list of 1 or 2 revisions. First revision is traffic target # "current" and second revision is traffic target "candidate". + # It is possible to specify `"@latest"` string as a shortcut to the lastest available revision. revisions: ["myservice-00013", "myservice-00015"] rolloutPercent: 50 # Percent [0-99] of traffic to route to "candidate" revision configuration: # serving.knative.dev/v1alpha1.ConfigurationSpec diff --git a/pkg/apis/serving/v1alpha1/service_types.go b/pkg/apis/serving/v1alpha1/service_types.go index 72ed2b73cfe9..767bb5a6dd94 100644 --- a/pkg/apis/serving/v1alpha1/service_types.go +++ b/pkg/apis/serving/v1alpha1/service_types.go @@ -128,6 +128,11 @@ type ReleaseType struct { Configuration ConfigurationSpec `json:"configuration,omitempty"` } +// ReleaseLatestRevisionKeyword is a shortcut for usage in the `release` mode +// to refer to the latest created revision. +// See #2819 for details. +const ReleaseLatestRevisionKeyword = "@latest" + // RunLatestType contains the options for always having a route to the latest configuration. See // ServiceSpec for more details. type RunLatestType struct { diff --git a/pkg/apis/serving/v1alpha1/service_validation.go b/pkg/apis/serving/v1alpha1/service_validation.go index d0bb51771e9d..e279bb459a68 100644 --- a/pkg/apis/serving/v1alpha1/service_validation.go +++ b/pkg/apis/serving/v1alpha1/service_validation.go @@ -99,6 +99,10 @@ func (rt *ReleaseType) Validate() *apis.FieldError { errs = errs.Also(apis.ErrOutOfBoundsValue(strconv.Itoa(numRevisions), "1", "2", "revisions")) } for i, r := range rt.Revisions { + // Skip over the last revision special keyword. + if r == ReleaseLatestRevisionKeyword { + continue + } if msgs := validation.IsDNS1035Label(r); len(msgs) > 0 { errs = errs.Also(apis.ErrInvalidArrayValue( fmt.Sprintf("not a DNS 1035 label: %v", msgs), "revisions", i)) diff --git a/pkg/apis/serving/v1alpha1/service_validation_test.go b/pkg/apis/serving/v1alpha1/service_validation_test.go index 6365b69d5f42..52a19398fa80 100644 --- a/pkg/apis/serving/v1alpha1/service_validation_test.go +++ b/pkg/apis/serving/v1alpha1/service_validation_test.go @@ -290,6 +290,28 @@ func TestServiceValidation(t *testing.T) { }, }, want: apis.ErrInvalidValue(incorrectDNS1035Label, "spec.release.revisions[0]"), + }, { + name: "valid release -- with @latest", + s: &Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid", + }, + Spec: ServiceSpec{ + Release: &ReleaseType{ + Revisions: []string{"s-1-00001", ReleaseLatestRevisionKeyword}, + Configuration: ConfigurationSpec{ + RevisionTemplate: RevisionTemplateSpec{ + Spec: RevisionSpec{ + Container: corev1.Container{ + Image: "hellworld", + }, + }, + }, + }, + }, + }, + }, + want: nil, }, { name: "invalid release -- too few revisions; empty slice", s: &Service{ diff --git a/pkg/reconciler/v1alpha1/service/resources/route.go b/pkg/reconciler/v1alpha1/service/resources/route.go index 9bcb3789bf78..7136a0c61524 100644 --- a/pkg/reconciler/v1alpha1/service/resources/route.go +++ b/pkg/reconciler/v1alpha1/service/resources/route.go @@ -44,21 +44,34 @@ func MakeRoute(service *v1alpha1.Service) (*v1alpha1.Route, error) { numRevisions := len(service.Spec.Release.Revisions) // Configure the 'current' route. - currentRevisionName := service.Spec.Release.Revisions[0] ttCurrent := v1alpha1.TrafficTarget{ - Name: "current", - Percent: 100 - rolloutPercent, - RevisionName: currentRevisionName, + Name: "current", + Percent: 100 - rolloutPercent, + } + currentRevisionName := service.Spec.Release.Revisions[0] + + // If the `current` revision refers to the well known name of the last + // known revision, use `Configuration` instead. + // Same for the `candidate` below. + // Part of #2819. + if currentRevisionName == v1alpha1.ReleaseLatestRevisionKeyword { + ttCurrent.ConfigurationName = names.Configuration(service) + } else { + ttCurrent.RevisionName = currentRevisionName } c.Spec.Traffic = append(c.Spec.Traffic, ttCurrent) // Configure the 'candidate' route. if numRevisions == 2 { - candidateRevisionName := service.Spec.Release.Revisions[1] ttCandidate := v1alpha1.TrafficTarget{ - Name: "candidate", - Percent: rolloutPercent, - RevisionName: candidateRevisionName, + Name: "candidate", + Percent: rolloutPercent, + } + candidateRevisionName := service.Spec.Release.Revisions[1] + if candidateRevisionName == v1alpha1.ReleaseLatestRevisionKeyword { + ttCandidate.ConfigurationName = names.Configuration(service) + } else { + ttCandidate.RevisionName = candidateRevisionName } c.Spec.Traffic = append(c.Spec.Traffic, ttCandidate) } diff --git a/pkg/reconciler/v1alpha1/service/resources/route_test.go b/pkg/reconciler/v1alpha1/service/resources/route_test.go index 79b89f16e441..eb277c98d440 100644 --- a/pkg/reconciler/v1alpha1/service/resources/route_test.go +++ b/pkg/reconciler/v1alpha1/service/resources/route_test.go @@ -19,7 +19,9 @@ package resources import ( "testing" + "github.com/google/go-cmp/cmp" "github.com/knative/serving/pkg/apis/serving" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" "github.com/knative/serving/pkg/reconciler/v1alpha1/service/resources/names" ) @@ -159,6 +161,130 @@ func TestRouteReleaseSingleRevision(t *testing.T) { } } +func TestRouteLatestRevisionSplit(t *testing.T) { + const ( + rolloutPercent = 42 + currentPercent = 100 - rolloutPercent + ) + s := createServiceWithRelease(2 /*num revisions*/, rolloutPercent) + s.Spec.Release.Revisions = []string{v1alpha1.ReleaseLatestRevisionKeyword, "juicy-revision"} + testConfigName := names.Configuration(s) + r, err := MakeRoute(s) + if err != nil { + t.Errorf("Expected nil for err got %q", err) + } + if got, want := r.Name, testServiceName; got != want { + t.Errorf("Expected %q for service name got %q", want, got) + } + if got, want := r.Namespace, testServiceNamespace; got != want { + t.Errorf("Expected %q for service namespace got %q", want, got) + } + wantT := []v1alpha1.TrafficTarget{{ + Name: "current", + Percent: currentPercent, + ConfigurationName: testConfigName, + }, { + Name: "candidate", + Percent: rolloutPercent, + RevisionName: "juicy-revision", + }, { + Name: "latest", + ConfigurationName: testConfigName, + }} + if got, want := r.Spec.Traffic, wantT; !cmp.Equal(got, want) { + t.Errorf("Traffic mismatch: diff (-got, +want): %s", cmp.Diff(got, want)) + } + expectOwnerReferencesSetCorrectly(t, r.OwnerReferences) + + wantL := map[string]string{ + testLabelKey: testLabelValueRelease, + serving.ServiceLabelKey: testServiceName, + } + if got, want := r.Labels, wantL; !cmp.Equal(got, want) { + t.Errorf("Labels mismatch: diff (-got, +want): %s", cmp.Diff(got, want)) + } +} +func TestRouteLatestRevisionSplitCandidate(t *testing.T) { + const ( + rolloutPercent = 42 + currentPercent = 100 - rolloutPercent + ) + s := createServiceWithRelease(2 /*num revisions*/, rolloutPercent) + s.Spec.Release.Revisions = []string{"squishy-revision", v1alpha1.ReleaseLatestRevisionKeyword} + testConfigName := names.Configuration(s) + r, err := MakeRoute(s) + if err != nil { + t.Errorf("Expected nil for err got %q", err) + } + if got, want := r.Name, testServiceName; got != want { + t.Errorf("Expected %q for service name got %q", want, got) + } + if got, want := r.Namespace, testServiceNamespace; got != want { + t.Errorf("Expected %q for service namespace got %q", want, got) + } + wantT := []v1alpha1.TrafficTarget{{ + Name: "current", + Percent: currentPercent, + RevisionName: "squishy-revision", + }, { + Name: "candidate", + Percent: rolloutPercent, + ConfigurationName: testConfigName, + }, { + Name: "latest", + ConfigurationName: testConfigName, + }} + if got, want := r.Spec.Traffic, wantT; !cmp.Equal(got, want) { + t.Errorf("Traffic mismatch: diff (-got, +want): %s", cmp.Diff(got, want)) + } + expectOwnerReferencesSetCorrectly(t, r.OwnerReferences) + + wantL := map[string]string{ + testLabelKey: testLabelValueRelease, + serving.ServiceLabelKey: testServiceName, + } + if got, want := r.Labels, wantL; !cmp.Equal(got, want) { + t.Errorf("Labels mismatch: diff (-got, +want): %s", cmp.Diff(got, want)) + } +} +func TestRouteLatestRevisionNoSplit(t *testing.T) { + s := createServiceWithRelease(1 /*num revisions*/, 0 /*unused*/) + s.Spec.Release.Revisions = []string{v1alpha1.ReleaseLatestRevisionKeyword} + testConfigName := names.Configuration(s) + r, err := MakeRoute(s) + + if err != nil { + t.Errorf("Expected nil for err got %q", err) + } + if got, want := r.Name, testServiceName; got != want { + t.Errorf("Expected %q for service name got %q", want, got) + } + if got, want := r.Namespace, testServiceNamespace; got != want { + t.Errorf("Expected %q for service namespace got %q", want, got) + } + // Should have 2 named traffic targets (current, latest) + wantT := []v1alpha1.TrafficTarget{{ + Name: "current", + Percent: 100, + ConfigurationName: testConfigName, + }, { + Name: "latest", + ConfigurationName: testConfigName, + }} + if got, want := r.Spec.Traffic, wantT; !cmp.Equal(got, want) { + t.Errorf("Traffic mismatch: diff (-got, +want): %s", cmp.Diff(got, want)) + } + expectOwnerReferencesSetCorrectly(t, r.OwnerReferences) + + wantL := map[string]string{ + testLabelKey: testLabelValueRelease, + serving.ServiceLabelKey: testServiceName, + } + if got, want := r.Labels, wantL; !cmp.Equal(got, want) { + t.Errorf("Labels mismatch: diff (-got, +want): %s", cmp.Diff(got, want)) + } +} + func TestRouteReleaseTwoRevisions(t *testing.T) { rolloutPercent := 48 currentPercent := 52 diff --git a/pkg/reconciler/v1alpha1/service/service_test.go b/pkg/reconciler/v1alpha1/service/service_test.go index 515ec6757da6..16cb17e38506 100644 --- a/pkg/reconciler/v1alpha1/service/service_test.go +++ b/pkg/reconciler/v1alpha1/service/service_test.go @@ -173,6 +173,26 @@ func TestReconcile(t *testing.T) { WantServiceReadyStats: map[string]int{ "foo/pinned3": 1, }, + }, { + Name: "release - with @latest", + Objects: []runtime.Object{ + svc("release", "foo", WithReleaseRollout(v1alpha1.ReleaseLatestRevisionKeyword)), + }, + Key: "foo/release", + WantCreates: []metav1.Object{ + config("release", "foo", WithReleaseRollout("release-00001")), + route("release", "foo", WithReleaseRollout(v1alpha1.ReleaseLatestRevisionKeyword)), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: svc("release", "foo", WithReleaseRollout(v1alpha1.ReleaseLatestRevisionKeyword), + // The first reconciliation will initialize the status conditions. + WithInitSvcConditions), + }}, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Created", "Created Configuration %q", "release"), + Eventf(corev1.EventTypeNormal, "Created", "Created Route %q", "release"), + Eventf(corev1.EventTypeNormal, "Updated", "Updated Service %q", "release"), + }, }, { Name: "release - create route and service", Objects: []runtime.Object{ @@ -324,6 +344,105 @@ func TestReconcile(t *testing.T) { WantEvents: []string{ Eventf(corev1.EventTypeNormal, "Updated", "Updated Service %q", "release-nr-ts2"), }, + }, { + Name: "release - route and config ready, using @latest", + Objects: []runtime.Object{ + svc("release-ready-lr", "foo", + WithReleaseRollout(v1alpha1.ReleaseLatestRevisionKeyword), WithInitSvcConditions), + route("release-ready-lr", "foo", + WithReleaseRollout(v1alpha1.ReleaseLatestRevisionKeyword), + RouteReady, WithDomain, WithDomainInternal, WithAddress, WithInitRouteConditions, + WithStatusTraffic([]v1alpha1.TrafficTarget{{ + Name: "current", + RevisionName: "release-ready-lr-00001", + Percent: 100, + }, { + Name: "latest", + RevisionName: "release-ready-lr-00001", + }}...), MarkTrafficAssigned, MarkIngressReady), + config("release-ready-lr", "foo", WithReleaseRollout("release-ready-lr"), WithGeneration(1), + // These turn a Configuration to Ready=true + WithLatestCreated, WithLatestReady), + }, + Key: "foo/release-ready-lr", + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: svc("release-ready-lr", "foo", + WithReleaseRollout(v1alpha1.ReleaseLatestRevisionKeyword), + // The delta induced by the config object. + WithReadyConfig("release-ready-lr-00001"), + // The delta induced by route object. + WithReadyRoute, WithSvcStatusDomain, WithSvcStatusAddress, + WithSvcStatusTraffic([]v1alpha1.TrafficTarget{{ + Name: "current", + RevisionName: "release-ready-lr-00001", + Percent: 100, + }, { + Name: "latest", + RevisionName: "release-ready-lr-00001", + }}...), + ), + }}, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Updated", "Updated Service %q", "release-ready-lr"), + }, + WantServiceReadyStats: map[string]int{ + "foo/release-ready-lr": 1, + }, + }, { + Name: "release - route and config ready, traffic split, using @latest", + Objects: []runtime.Object{ + svc("release-ready-lr", "foo", + WithReleaseRolloutAndPercentage( + 42, "release-ready-lr-00001", v1alpha1.ReleaseLatestRevisionKeyword), WithInitSvcConditions), + route("release-ready-lr", "foo", + WithReleaseRolloutAndPercentage( + 42, "release-ready-lr-00001", v1alpha1.ReleaseLatestRevisionKeyword), + RouteReady, WithDomain, WithDomainInternal, WithAddress, WithInitRouteConditions, + WithStatusTraffic([]v1alpha1.TrafficTarget{{ + Name: "current", + RevisionName: "release-ready-lr-00001", + Percent: 58, + }, { + Name: "candidate", + RevisionName: "release-ready-lr-00002", + Percent: 42, + }, { + Name: "latest", + RevisionName: "release-ready-lr-00002", + }}...), MarkTrafficAssigned, MarkIngressReady), + config("release-ready-lr", "foo", WithReleaseRollout("release-ready-lr"), WithGeneration(2), + // These turn a Configuration to Ready=true + WithLatestCreated, WithLatestReady), + }, + Key: "foo/release-ready-lr", + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: svc("release-ready-lr", "foo", + WithReleaseRolloutAndPercentage( + 42, "release-ready-lr-00001", v1alpha1.ReleaseLatestRevisionKeyword), + // The delta induced by the config object. + WithReadyConfig("release-ready-lr-00002"), + // The delta induced by route object. + WithReadyRoute, WithSvcStatusDomain, WithSvcStatusAddress, + WithSvcStatusTraffic([]v1alpha1.TrafficTarget{{ + Name: "current", + RevisionName: "release-ready-lr-00001", + Percent: 58, + }, { + Name: "candidate", + RevisionName: "release-ready-lr-00002", + Percent: 42, + }, { + Name: "latest", + RevisionName: "release-ready-lr-00002", + }}...), + ), + }}, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "Updated", "Updated Service %q", "release-ready-lr"), + }, + WantServiceReadyStats: map[string]int{ + "foo/release-ready-lr": 1, + }, }, { Name: "release - route and config ready, propagate ready, percentage set", Objects: []runtime.Object{ diff --git a/test/conformance/service_test.go b/test/conformance/service_test.go index 74c97b621341..46cf6d95e79e 100644 --- a/test/conformance/service_test.go +++ b/test/conformance/service_test.go @@ -24,6 +24,7 @@ import ( "strconv" "testing" + "github.com/google/go-cmp/cmp" pkgTest "github.com/knative/pkg/test" "github.com/knative/pkg/test/logging" "github.com/knative/serving/pkg/apis/serving/v1alpha1" @@ -136,6 +137,18 @@ func validateLabelsPropagation(logger *logging.BaseLogger, objects test.Resource return nil } +func validateReleaseServiceShape(objs *test.ResourceObjects) error { + // Check that Spec.Revisions is as expected. + if got, want := objs.Service.Spec.Release.Revisions, []string{v1alpha1.ReleaseLatestRevisionKeyword}; !cmp.Equal(got, want) { + return fmt.Errorf("Spec.Release.Revisions mismatch: diff: %s", cmp.Diff(got, want)) + } + // Traffic should be routed to the lastest created revision. + if got, want := objs.Service.Status.Traffic[0].RevisionName, objs.Config.Status.LatestReadyRevisionName; got != want { + return fmt.Errorf("Status.Traffic[0].RevisionsName = %s, want: %s", got, want) + } + return nil +} + // TestRunLatestService tests both Creation and Update paths of a runLatest service. The test performs a series of Update/Validate steps to ensure that // the service transitions as expected during each step. // Currently the test performs the following updates: @@ -315,15 +328,19 @@ func TestReleaseService(t *testing.T) { expectedThirdRev = helloWorldText ) - objects, err := test.CreateRunLatestServiceReady(logger, clients, &names, &test.Options{}) + objects, err := test.CreateReleaseServiceWithLatest(logger, clients, &names, &test.Options{}) if err != nil { t.Fatalf("Failed to create initial Service %v: %v", names.Service, err) } - firstRevision := names.Revision + logger.Info("Validating service shape.") + if err := validateReleaseServiceShape(objects); err != nil { + t.Fatalf("Release shape incorrect: %v", err) + } + revisions := []string{names.Revision} // One Revision Specified, current == latest. logger.Info("Updating Service to ReleaseType using lastCreatedRevision") - objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, []string{firstRevision}, 0) + objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, revisions, 0) if err != nil { t.Fatalf("Service %s was not updated to release: %v", names.Service, err) } @@ -345,7 +362,7 @@ func TestReleaseService(t *testing.T) { if names.Revision, err = test.WaitForServiceLatestRevision(clients, names); err != nil { t.Fatalf("The Service %s was not updated with new revision %s: %v", names.Service, names.Revision, err) } - secondRevision := names.Revision + revisions = append(revisions, names.Revision) logger.Info("Since the Service is using release the Route will not be updated, but new revision will be available at 'latest'") validateDomains(t, logger, clients, @@ -356,7 +373,7 @@ func TestReleaseService(t *testing.T) { // Two Revisions Specified, 50% rollout, candidate == latest. logger.Info("Updating Service to split traffic between two revisions using Release mode") - if objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, []string{firstRevision, secondRevision}, 50); err != nil { + if objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, revisions, 50); err != nil { t.Fatalf("Service %s was not updated to release: %v", names.Service, err) } diff --git a/test/crd.go b/test/crd.go index 451dd3ca589a..cf6628cb1d4a 100644 --- a/test/crd.go +++ b/test/crd.go @@ -178,6 +178,29 @@ func LatestService(namespace string, names ResourceNames, options *Options, fopt return svc } +// ReleaseLatestService returns a Release Service object in namespace with the name names.Service +// that uses the image specified by names.Image and `@latest` as the only revision. +func ReleaseLatestService(namespace string, names ResourceNames, options *Options, fopt ...testing.ServiceOption) *v1alpha1.Service { + svc := &v1alpha1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: names.Service, + }, + Spec: v1alpha1.ServiceSpec{ + Release: &v1alpha1.ReleaseType{ + Revisions: []string{v1alpha1.ReleaseLatestRevisionKeyword}, + Configuration: *ConfigurationSpec(ImagePath(names.Image), options), + }, + }, + } + + // Apply any mutations we have been provided. + for _, opt := range fopt { + opt(svc) + } + return svc +} + // ReleaseService returns a Release Service object in namespace with the name names.Service that uses // the image specified by names.Image. It also takes a list of 1-2 revisons and a rolloutPercent to be // used to configure routing diff --git a/test/service.go b/test/service.go index b6d4d4708895..f829ffb25e37 100644 --- a/test/service.go +++ b/test/service.go @@ -95,6 +95,44 @@ func getResourceObjects(clients *Clients, names ResourceNames) (*ResourceObjects }, nil } +// CreateReleaseServiceWithLatest creates a `Release` service using `@latest` +// as the only revision. +// This function expects `Service` and `Image` name passed in through `names`. +// Names is updated with the `Route` and `Configuration` created by the Service +// and `ResourceObjects` is returned with the `Service`, `Route`, and `Configuration` objects. +// Returns an error if the service does not come up correctly. +func CreateReleaseServiceWithLatest( + logger *logging.BaseLogger, clients *Clients, + names *ResourceNames, options *Options) (*ResourceObjects, error) { + if names.Service == "" || names.Image == "" { + return nil, fmt.Errorf("expected non-empty Service and Image name; got Service = %s, Image = %s", names.Service, names.Image) + } + + logger.Info("Creating a new Service as Release with @latest.") + svc, err := CreateReleaseService(logger, clients, *names, options) + if err != nil { + return nil, err + } + + // Populate Route and Configuration Objects with name + names.Route = serviceresourcenames.Route(svc) + names.Config = serviceresourcenames.Configuration(svc) + + logger.Info("Waiting for Service to transition to Ready.") + if err := WaitForServiceState(clients.ServingClient, names.Service, IsServiceReady, "ServiceIsReady"); err != nil { + return nil, err + } + + logger.Info("Checking to ensure Service Status is populated for Ready service.") + err = validateCreatedServiceStatus(clients, names) + if err != nil { + return nil, err + } + + logger.Info("Getting latest objects Created by Service.") + return getResourceObjects(clients, *names) +} + // CreateRunLatestServiceReady creates a new RunLatest Service in state 'Ready'. This function expects Service and Image name passed in through 'names'. // Names is updated with the Route and Configuration created by the Service and ResourceObjects is returned with the Service, Route, and Configuration objects. // Returns error if the service does not come up correctly. @@ -128,6 +166,14 @@ func CreateRunLatestServiceReady(logger *logging.BaseLogger, clients *Clients, n return getResourceObjects(clients, *names) } +// CreateReleaseService creates a service in namespace with the name names.Service and names.Image, +// configured with `@latest` revision. +func CreateReleaseService(logger *logging.BaseLogger, clients *Clients, names ResourceNames, options *Options, fopt ...testing.ServiceOption) (*v1alpha1.Service, error) { + service := ReleaseLatestService(ServingNamespace, names, options, fopt...) + LogResourceObject(logger, ResourceObjects{Service: service}) + return clients.ServingClient.Services.Create(service) +} + // CreateLatestService creates a service in namespace with the name names.Service and names.Image func CreateLatestService(logger *logging.BaseLogger, clients *Clients, names ResourceNames, options *Options, fopt ...testing.ServiceOption) (*v1alpha1.Service, error) { service := LatestService(ServingNamespace, names, options, fopt...) diff --git a/test/util.go b/test/util.go index 9a8d500bdcff..1cc15eb86910 100644 --- a/test/util.go +++ b/test/util.go @@ -15,10 +15,10 @@ package test import ( "context" - "encoding/json" "fmt" "net/http" + "github.com/davecgh/go-spew/spew" "github.com/knative/pkg/signals" "github.com/knative/pkg/test/logging" ) @@ -27,12 +27,7 @@ import ( // LogResourceObject logs the resource object with the resource name and value func LogResourceObject(logger *logging.BaseLogger, value ResourceObjects) { - // Log the route object - if resourceJSON, err := json.Marshal(value); err != nil { - logger.Infof("Failed to create json from resource object: %v", err) - } else { - logger.Infof("resource %s", string(resourceJSON)) - } + logger.Infof("resource %s", spew.Sdump(value)) } // ImagePath is a helper function to prefix image name with repo and suffix with tag