diff --git a/gwctl/pkg/common/testhelpers.go b/gwctl/pkg/common/testhelpers.go index b971d7928e..2874538a98 100644 --- a/gwctl/pkg/common/testhelpers.go +++ b/gwctl/pkg/common/testhelpers.go @@ -17,6 +17,8 @@ limitations under the License. package common import ( + "encoding/json" + "fmt" "strings" "github.com/google/go-cmp/cmp" @@ -51,3 +53,21 @@ var YamlStringTransformer = cmp.Transformer("YamlLines", func(s YamlString) []st } return lines[start : end+1] }) + +type JsonString string + +func (src JsonString) CmpDiff(tgt JsonString) (diff string, err error) { + var srcMap, targetMap map[string]interface{} + err = json.Unmarshal([]byte(src), &srcMap) + if err != nil { + err = fmt.Errorf("failed to unmarshal the source json: %w", err) + return + } + err = json.Unmarshal([]byte(tgt), &targetMap) + if err != nil { + err = fmt.Errorf("failed to unmarshal the target json: %w", err) + return + } + + return cmp.Diff(srcMap, targetMap), nil +} diff --git a/gwctl/pkg/printer/gatewayclasses_test.go b/gwctl/pkg/printer/gatewayclasses_test.go index 7aef24d771..ff716f5e03 100644 --- a/gwctl/pkg/printer/gatewayclasses_test.go +++ b/gwctl/pkg/printer/gatewayclasses_test.go @@ -18,6 +18,7 @@ package printer import ( "bytes" + "fmt" "testing" "time" @@ -29,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" testingclock "k8s.io/utils/clock/testing" + apisv1alpha2 "sigs.k8s.io/gateway-api/apis/applyconfiguration/apis/v1alpha2" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -296,3 +298,167 @@ foo-com-internal-gateway-class foo-com-internal-gateway-class/controller True t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } + +// TestGatewayClassesPrinter_PrintJson tests the -o json output of the `get` subcommand +func TestGatewayClassesPrinter_PrintJson(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-365 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + gtwName := "foo-com-internal-gateway-class" + gtwApplyConfig := apisv1alpha2.GatewayClass(gtwName) + + gtwObject := &gatewayv1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + APIVersion: *gtwApplyConfig.APIVersion, + Kind: *gtwApplyConfig.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-com-internal-gateway-class", + Labels: map[string]string{"app": "foo", "env": "internal"}, + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(gtwName + "/controller"), + }, + Status: gatewayv1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }, + }, + } + gtwObject.APIVersion = *gtwApplyConfig.APIVersion + gtwObject.Kind = *gtwApplyConfig.Kind + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, gtwObject)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForGatewayClass(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + gcp := &GatewayClassesPrinter{ + Out: params.Out, + Clock: fakeClock, + } + gcp.PrintJson(resourceModel) + + got := common.JsonString(params.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` +{ +"kind": "GatewayClass", +"apiVersion": "gateway.networking.k8s.io/v1alpha2", + "metadata": { + "name": "foo-com-internal-gateway-class", + "resourceVersion": "999", + "creationTimestamp": "%s", + "labels": { + "app": "foo", + "env": "internal" + } + }, + "spec": { + "controllerName": "foo-com-internal-gateway-class/controller" + }, + "status": { + "conditions": [ + { + "type": "Accepted", + "status": "True", + "lastTransitionTime": null, + "reason": "", + "message": "" + } + ] + } +}`, creationTime.Format(time.RFC3339))) + diff, err := want.CmpDiff(got) + if err != nil { + t.Fatalf("Failed to compare the json diffs: %v", diff) + } + if diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + +// TestGatewayClassesPrinter_PrintYaml tests the -o yaml output of the `get` subcommand +func TestGatewayClassesPrinter_PrintYaml(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-365 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + gtwName := "foo-com-internal-gateway-class" + gtwApplyConfig := apisv1alpha2.GatewayClass(gtwName) + + object := &gatewayv1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + APIVersion: *gtwApplyConfig.APIVersion, + Kind: *gtwApplyConfig.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: gtwName, + Labels: map[string]string{"app": "foo", "env": "internal"}, + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(gtwName + "/controller"), + }, + Status: gatewayv1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }, + }, + } + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, object)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForGatewayClass(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + gcp := &GatewayClassesPrinter{ + Out: params.Out, + Clock: fakeClock, + } + + gcp.PrintYaml(resourceModel) + + got := params.Out.(*bytes.Buffer).String() + want := fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: GatewayClass +metadata: + creationTimestamp: "%s" + labels: + app: foo + env: internal + name: foo-com-internal-gateway-class + resourceVersion: "999" +spec: + controllerName: foo-com-internal-gateway-class/controller +status: + conditions: + - lastTransitionTime: null + message: "" + reason: "" + status: "True" + type: Accepted +`, creationTime.Format(time.RFC3339)) + if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} diff --git a/gwctl/pkg/printer/gateways_test.go b/gwctl/pkg/printer/gateways_test.go index 81a125b027..2ea7f08a5f 100644 --- a/gwctl/pkg/printer/gateways_test.go +++ b/gwctl/pkg/printer/gateways_test.go @@ -18,6 +18,8 @@ package printer import ( "bytes" + "fmt" + apisv1alpha2 "sigs.k8s.io/gateway-api/apis/applyconfiguration/apis/v1alpha2" "testing" "time" @@ -449,3 +451,230 @@ gateway-2 gatewayclass-1 192.168.100.5 8080 False 5d t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } + +// TestGatewaysPrinter_PrintJson tests the -o json output of the `get` subcommand +func TestGatewaysPrinter_PrintJson(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-5 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + gcName := "gateway-1" + gcApplyConfig := apisv1alpha2.Gateway(gcName, "") + + gcObject := &gatewayv1.Gateway{ + TypeMeta: metav1.TypeMeta{ + APIVersion: *gcApplyConfig.APIVersion, + Kind: *gcApplyConfig.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + Labels: map[string]string{"app": "foo", "env": "internal"}, + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "gatewayclass-1", + Listeners: []gatewayv1.Listener{ + { + Name: "http-8080", + Protocol: gatewayv1.HTTPProtocolType, + Port: gatewayv1.PortNumber(8080), + }, + }, + }, + Status: gatewayv1.GatewayStatus{ + Addresses: []gatewayv1.GatewayStatusAddress{ + { + Value: "192.168.100.5", + }, + }, + Conditions: []metav1.Condition{ + { + Type: "Programmed", + Status: "False", + }, + }, + }, + } + + objects := []runtime.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass-1", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: "example.net/gateway-controller", + Description: common.PtrTo("random"), + }, + }, + gcObject, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForGateway(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + gp := &GatewaysPrinter{ + Out: params.Out, + Clock: fakeClock, + } + gp.PrintJson(resourceModel) + + got := common.JsonString(params.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` + { + "kind": "Gateway", + "apiVersion": "gateway.networking.k8s.io/v1alpha2", + "metadata": { + "name": "gateway-1", + "resourceVersion": "999", + "creationTimestamp": "%s", + "labels": { + "app": "foo", + "env": "internal" + } + }, + "spec": { + "gatewayClassName": "gatewayclass-1", + "listeners": [ + { + "name": "http-8080", + "port": 8080, + "protocol": "HTTP" + } + ] + }, + "status": { + "addresses": [ + { + "value": "192.168.100.5" + } + ], + "conditions": [ + { + "type": "Programmed", + "status": "False", + "lastTransitionTime": null, + "reason": "", + "message": "" + } + ] + } + }`, creationTime.Format(time.RFC3339))) + diff, err := want.CmpDiff(got) + if err != nil { + t.Fatalf("Failed to compare the json diffs: %v", diff) + } + if diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + +// TestGatewaysPrinter_PrintYaml tests the -o yaml output of the `get` subcommand +func TestGatewaysPrinter_PrintYaml(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-5 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + gcName := "gateway-1" + gcApplyConfig := apisv1alpha2.Gateway(gcName, "") + + gcObject := &gatewayv1.Gateway{ + TypeMeta: metav1.TypeMeta{ + APIVersion: *gcApplyConfig.APIVersion, + Kind: *gcApplyConfig.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + Labels: map[string]string{"app": "foo", "env": "internal"}, + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "gatewayclass-1", + Listeners: []gatewayv1.Listener{ + { + Name: "http-8080", + Protocol: gatewayv1.HTTPProtocolType, + Port: gatewayv1.PortNumber(8080), + }, + }, + }, + Status: gatewayv1.GatewayStatus{ + Addresses: []gatewayv1.GatewayStatusAddress{ + { + Value: "192.168.100.5", + }, + }, + Conditions: []metav1.Condition{ + { + Type: "Programmed", + Status: "False", + }, + }, + }, + } + + objects := []runtime.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass-1", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: "example.net/gateway-controller", + Description: common.PtrTo("random"), + }, + }, + gcObject, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForGateway(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + gp := &GatewaysPrinter{ + Out: params.Out, + Clock: fakeClock, + } + gp.PrintYaml(resourceModel) + + got := common.JsonString(params.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + creationTimestamp: "%s" + labels: + app: foo + env: internal + name: gateway-1 + resourceVersion: "999" +spec: + gatewayClassName: gatewayclass-1 + listeners: + - name: http-8080 + port: 8080 + protocol: HTTP +status: + addresses: + - value: 192.168.100.5 + conditions: + - lastTransitionTime: null + message: "" + reason: "" + status: "False" + type: Programmed`, creationTime.Format(time.RFC3339))) + if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} diff --git a/gwctl/pkg/printer/httproutes_test.go b/gwctl/pkg/printer/httproutes_test.go index 0d425dd9e4..954b1c1bd8 100644 --- a/gwctl/pkg/printer/httproutes_test.go +++ b/gwctl/pkg/printer/httproutes_test.go @@ -18,6 +18,8 @@ package printer import ( "bytes" + "fmt" + apisv1alpha2 "sigs.k8s.io/gateway-api/apis/applyconfiguration/apis/v1alpha2" "testing" "time" @@ -519,3 +521,207 @@ default httproute-2 example.com 1 24h t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } + +// TestHTTPRoutesPrinter_PrintJson tests the correctness of JSON output associated with -o json of `get` subcommand +func TestHTTPRoutesPrinter_PrintJson(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + hrName, hrNamespace := "httproute-1", "default" + hrConfig := apisv1alpha2.HTTPRoute(hrName, hrNamespace) + + objects := []runtime.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass-1", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: "example.net/gateway-controller", + Description: common.PtrTo("random"), + }, + }, + + &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-1", + Namespace: "default", + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "gatewayclass-1", + }, + }, + &gatewayv1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + APIVersion: *hrConfig.APIVersion, + Kind: *hrConfig.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: hrName, + Namespace: hrNamespace, + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + Labels: map[string]string{"app": "foo", "env": "internal"}, + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"example.com"}, + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{ + { + Name: "gateway-1", + }, + }, + }, + }, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + + resourceModel, err := discoverer.DiscoverResourcesForHTTPRoute(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to discover resources: %v", err) + } + + hp := &HTTPRoutesPrinter{ + Out: params.Out, + Clock: fakeClock, + } + hp.PrintJson(resourceModel) + + got := common.JsonString(params.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` + { + "kind": "HTTPRoute", + "apiVersion": "gateway.networking.k8s.io/v1alpha2", + "metadata": { + "name": "httproute-1", + "namespace": "default", + "resourceVersion": "999", + "creationTimestamp": "%s", + "labels": { + "app": "foo", + "env": "internal" + } + }, + "spec": { + "parentRefs": [ + { + "name": "gateway-1" + } + ], + "hostnames": [ + "example.com" + ] + }, + "status": { + "parents": null + } + }`, creationTime.Format(time.RFC3339))) + diff, err := want.CmpDiff(got) + if err != nil { + t.Fatalf("Failed to compare the json diffs: %v", diff) + } + if diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + +// TestHTTPRoutesPrinter_PrintYaml tests the correctness of YAML output associated with -o yaml of `get` subcommand +func TestHTTPRoutesPrinter_PrintYaml(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + hrName, hrNamespace := "httproute-1", "default" + hrConfig := apisv1alpha2.HTTPRoute(hrName, hrNamespace) + + objects := []runtime.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass-1", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: "example.net/gateway-controller", + Description: common.PtrTo("random"), + }, + }, + + &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-1", + Namespace: "default", + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "gatewayclass-1", + }, + }, + &gatewayv1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + APIVersion: *hrConfig.APIVersion, + Kind: *hrConfig.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: hrName, + Namespace: hrNamespace, + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + Labels: map[string]string{"app": "foo", "env": "internal"}, + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"example.com"}, + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{ + { + Name: "gateway-1", + }, + }, + }, + }, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + + resourceModel, err := discoverer.DiscoverResourcesForHTTPRoute(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to discover resources: %v", err) + } + + hp := &HTTPRoutesPrinter{ + Out: params.Out, + Clock: fakeClock, + } + hp.PrintYaml(resourceModel) + + got := common.JsonString(params.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: HTTPRoute +metadata: + creationTimestamp: "%s" + labels: + app: foo + env: internal + name: httproute-1 + namespace: default + resourceVersion: "999" +spec: + hostnames: + - example.com + parentRefs: + - name: gateway-1 +status: + parents: null`, creationTime.Format(time.RFC3339))) + if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} diff --git a/gwctl/pkg/printer/namespace_test.go b/gwctl/pkg/printer/namespace_test.go index 0b39e6bf6c..9cce387ebe 100644 --- a/gwctl/pkg/printer/namespace_test.go +++ b/gwctl/pkg/printer/namespace_test.go @@ -18,6 +18,7 @@ package printer import ( "bytes" + "fmt" "testing" "time" @@ -301,7 +302,7 @@ func TestNamespacesPrinter_LabelSelector(t *testing.T) { Out: params.Out, Clock: fakeClock, } - nsp.Print(resourceModel) + nsp.PrintTable(resourceModel) got := params.Out.(*bytes.Buffer).String() want := ` @@ -313,3 +314,139 @@ namespace-2 Active 46d t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } + +// TestNamespacesPrinter_PrintJson tests the correctness of JSON output associated with -o json of `get` subcommand +func TestNamespacesPrinter_PrintJson(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-46 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + nsObject := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "v1", + APIVersion: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace-1", + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + Labels: map[string]string{"app": "foo", "env": "internal"}, + }, + Spec: corev1.NamespaceSpec{ + Finalizers: []corev1.FinalizerName{"kubernetes"}, + }, + Status: corev1.NamespaceStatus{ + Phase: corev1.NamespaceActive, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, nsObject)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForNamespace(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + nsp := &NamespacesPrinter{ + Out: params.Out, + Clock: fakeClock, + } + nsp.PrintJson(resourceModel) + + got := common.JsonString(params.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` + { + "kind": "v1", + "apiVersion": "Namespace", + "metadata": { + "name": "namespace-1", + "resourceVersion": "999", + "creationTimestamp": "%s", + "labels": { + "app": "foo", + "env": "internal" + } + }, + "spec": { + "finalizers": [ + "kubernetes" + ] + }, + "status": { + "phase": "Active" + } + }`, creationTime.Format(time.RFC3339))) + diff, err := want.CmpDiff(got) + if err != nil { + t.Fatalf("Failed to compare the json diffs: %v", diff) + } + if diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + +// TestNamespacesPrinter_PrintYaml tests the correctness of YAML output associated with -o yaml of `get` subcommand +func TestNamespacesPrinter_PrintYaml(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime := fakeClock.Now().Add(-46 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + nsObject := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "v1", + APIVersion: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace-1", + CreationTimestamp: metav1.Time{ + Time: creationTime, + }, + Labels: map[string]string{"app": "foo", "env": "internal"}, + }, + Spec: corev1.NamespaceSpec{ + Finalizers: []corev1.FinalizerName{"kubernetes"}, + }, + Status: corev1.NamespaceStatus{ + Phase: corev1.NamespaceActive, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, nsObject)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForNamespace(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + nsp := &NamespacesPrinter{ + Out: params.Out, + Clock: fakeClock, + } + nsp.PrintYaml(resourceModel) + + got := params.Out.(*bytes.Buffer).String() + want := fmt.Sprintf(` +apiVersion: Namespace +kind: v1 +metadata: + creationTimestamp: "%s" + labels: + app: foo + env: internal + name: namespace-1 + resourceVersion: "999" +spec: + finalizers: + - kubernetes +status: + phase: Active +`, creationTime.Format(time.RFC3339)) + if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} diff --git a/gwctl/pkg/printer/policies_test.go b/gwctl/pkg/printer/policies_test.go index c03380e99b..667b3392e9 100644 --- a/gwctl/pkg/printer/policies_test.go +++ b/gwctl/pkg/printer/policies_test.go @@ -18,6 +18,7 @@ package printer import ( "bytes" + "fmt" "testing" "time" @@ -355,6 +356,351 @@ timeoutpolicies.bar.com Direct Namespaced 5m } } +// TestPoliciesPrinter_PrintCRDs_Json tests the correctness of JSON output associated with -o json of `get` subcommand +func TestPoliciesPrinter_PrintCRDs_Json(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime1 := fakeClock.Now().Add(-24 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + creationTime2 := fakeClock.Now().Add(-5 * time.Minute).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + objects := []runtime.Object{ + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "healthcheckpolicies.foo.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "inherited", + }, + CreationTimestamp: metav1.Time{ + Time: creationTime1, + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.ClusterScoped, + Group: "foo.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "healthcheckpolicies", + Kind: "HealthCheckPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "foo.com/v1", + "kind": "HealthCheckPolicy", + "metadata": map[string]interface{}{ + "name": "health-check-gateway", + }, + "spec": map[string]interface{}{ + "override": map[string]interface{}{ + "key1": "value-child-1", + }, + "default": map[string]interface{}{ + "key2": "value-child-2", + "key5": "value-child-5", + }, + "targetRef": map[string]interface{}{ + "group": "gateway.networking.k8s.io", + "kind": "Gateway", + "name": "foo-gateway", + "namespace": "default", + }, + }, + }, + }, + + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "timeoutpolicies.bar.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "direct", + }, + CreationTimestamp: metav1.Time{ + Time: creationTime2, + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.NamespaceScoped, + Group: "bar.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "timeoutpolicies", + Kind: "TimeoutPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bar.com/v1", + "kind": "TimeoutPolicy", + "metadata": map[string]interface{}{ + "name": "timeout-policy-namespace", + }, + "spec": map[string]interface{}{ + "condition": "path=/abc", + "seconds": int64(30), + "targetRef": map[string]interface{}{ + "kind": "Namespace", + "name": "default", + }, + }, + }, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + pp := &PoliciesPrinter{ + Out: &bytes.Buffer{}, + Clock: fakeClock, + } + pp.printCRDsJson(params.PolicyManager.GetCRDs()) + + got := common.JsonString(pp.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(`{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "creationTimestamp": "%s", + "labels": { + "gateway.networking.k8s.io/policy": "inherited" + }, + "name": "healthcheckpolicies.foo.com", + "resourceVersion": "999" + }, + "spec": { + "group": "foo.com", + "names": { + "kind": "HealthCheckPolicy", + "plural": "healthcheckpolicies" + }, + "scope": "Cluster", + "versions": [ + { + "name": "v1", + "served": false, + "storage": false + } + ] + }, + "status": { + "acceptedNames": { + "kind": "", + "plural": "" + }, + "conditions": null, + "storedVersions": null + } + }, + { + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "creationTimestamp": "%s", + "labels": { + "gateway.networking.k8s.io/policy": "direct" + }, + "name": "timeoutpolicies.bar.com", + "resourceVersion": "999" + }, + "spec": { + "group": "bar.com", + "names": { + "kind": "TimeoutPolicy", + "plural": "timeoutpolicies" + }, + "scope": "Namespaced", + "versions": [ + { + "name": "v1", + "served": false, + "storage": false + } + ] + }, + "status": { + "acceptedNames": { + "kind": "", + "plural": "" + }, + "conditions": null, + "storedVersions": null + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "" + } + }`, creationTime1.Format(time.RFC3339), creationTime2.Format(time.RFC3339))) + diff, err := want.CmpDiff(got) + if err != nil { + t.Fatalf("Failed to compare the json diffs: %v", diff) + } + if diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + +// TestPoliciesPrinter_PrintCRDs_Yaml tests the correctness of YAML output associated with -o yaml of `get` subcommand +func TestPoliciesPrinter_PrintCRDs_Yaml(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + creationTime1 := fakeClock.Now().Add(-24 * 24 * time.Hour).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + creationTime2 := fakeClock.Now().Add(-5 * time.Minute).UTC() // UTC being necessary for consistently handling the time while marshalling/unmarshalling its JSON + + objects := []runtime.Object{ + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "healthcheckpolicies.foo.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "inherited", + }, + CreationTimestamp: metav1.Time{ + Time: creationTime1, + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.ClusterScoped, + Group: "foo.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "healthcheckpolicies", + Kind: "HealthCheckPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "foo.com/v1", + "kind": "HealthCheckPolicy", + "metadata": map[string]interface{}{ + "name": "health-check-gateway", + }, + "spec": map[string]interface{}{ + "override": map[string]interface{}{ + "key1": "value-child-1", + }, + "default": map[string]interface{}{ + "key2": "value-child-2", + "key5": "value-child-5", + }, + "targetRef": map[string]interface{}{ + "group": "gateway.networking.k8s.io", + "kind": "Gateway", + "name": "foo-gateway", + "namespace": "default", + }, + }, + }, + }, + + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "timeoutpolicies.bar.com", + Labels: map[string]string{ + gatewayv1alpha2.PolicyLabelKey: "direct", + }, + CreationTimestamp: metav1.Time{ + Time: creationTime2, + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Scope: apiextensionsv1.NamespaceScoped, + Group: "bar.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{Name: "v1"}}, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "timeoutpolicies", + Kind: "TimeoutPolicy", + }, + }, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "bar.com/v1", + "kind": "TimeoutPolicy", + "metadata": map[string]interface{}{ + "name": "timeout-policy-namespace", + }, + "spec": map[string]interface{}{ + "condition": "path=/abc", + "seconds": int64(30), + "targetRef": map[string]interface{}{ + "kind": "Namespace", + "name": "default", + }, + }, + }, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + pp := &PoliciesPrinter{ + Out: &bytes.Buffer{}, + Clock: fakeClock, + } + pp.printCRDsYaml(params.PolicyManager.GetCRDs()) + + got := common.JsonString(pp.Out.(*bytes.Buffer).String()) + want := common.JsonString(fmt.Sprintf(` +apiVersion: v1 +items: +- apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + creationTimestamp: "%s" + labels: + gateway.networking.k8s.io/policy: inherited + name: healthcheckpolicies.foo.com + resourceVersion: "999" + spec: + group: foo.com + names: + kind: HealthCheckPolicy + plural: healthcheckpolicies + scope: Cluster + versions: + - name: v1 + served: false + storage: false + status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +- apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + creationTimestamp: "%s" + labels: + gateway.networking.k8s.io/policy: direct + name: timeoutpolicies.bar.com + resourceVersion: "999" + spec: + group: bar.com + names: + kind: TimeoutPolicy + plural: timeoutpolicies + scope: Namespaced + versions: + - name: v1 + served: false + storage: false + status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +kind: List +metadata: + resourceVersion: ""`, creationTime1.Format(time.RFC3339), creationTime2.Format(time.RFC3339))) + if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { + t.Errorf("PrintDescribeView: Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + func TestPolicyCrd_PrintDescribeView(t *testing.T) { objects := []runtime.Object{ &apiextensionsv1.CustomResourceDefinition{