diff --git a/gwctl/cmd/describe.go b/gwctl/cmd/describe.go index 14a500f643..8469c28137 100644 --- a/gwctl/cmd/describe.go +++ b/gwctl/cmd/describe.go @@ -28,11 +28,13 @@ import ( "sigs.k8s.io/gateway-api/gwctl/pkg/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" ) func NewDescribeCommand() *cobra.Command { var namespaceFlag string var allNamespacesFlag bool + var labelSelector string cmd := &cobra.Command{ Use: "describe {policies|httproutes|gateways|gatewayclasses|backends|namespace} RESOURCE_NAME", @@ -45,6 +47,7 @@ func NewDescribeCommand() *cobra.Command { } cmd.Flags().StringVarP(&namespaceFlag, "namespace", "n", "default", "") cmd.Flags().BoolVarP(&allNamespacesFlag, "all-namespaces", "A", false, "If present, list requested resources from all namespaces.") + cmd.Flags().StringVarP(&labelSelector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.") return cmd } @@ -64,6 +67,12 @@ func runDescribe(cmd *cobra.Command, args []string, params *utils.CmdParams) { os.Exit(1) } + labelSelector, err := cmd.Flags().GetString("selector") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read flag \"selector\": %v\n", err) + os.Exit(1) + } + if allNs { ns = metav1.NamespaceAll } @@ -97,7 +106,15 @@ func runDescribe(cmd *cobra.Command, args []string, params *utils.CmdParams) { policiesPrinter.PrintDescribeView(policyList) case "httproute", "httproutes": - filter := resourcediscovery.Filter{Namespace: ns} + selector, err := labels.Parse(labelSelector) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + os.Exit(1) + } + filter := resourcediscovery.Filter{ + Namespace: ns, + Labels: selector, + } if len(args) > 1 { filter.Name = args[1] } @@ -109,7 +126,15 @@ func runDescribe(cmd *cobra.Command, args []string, params *utils.CmdParams) { httpRoutesPrinter.PrintDescribeView(resourceModel) case "gateway", "gateways": - filter := resourcediscovery.Filter{Namespace: ns} + selector, err := labels.Parse(labelSelector) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + os.Exit(1) + } + filter := resourcediscovery.Filter{ + Namespace: ns, + Labels: selector, + } if len(args) > 1 { filter.Name = args[1] } @@ -121,7 +146,14 @@ func runDescribe(cmd *cobra.Command, args []string, params *utils.CmdParams) { gwPrinter.PrintDescribeView(resourceModel) case "gatewayclass", "gatewayclasses": - filter := resourcediscovery.Filter{} + selector, err := labels.Parse(labelSelector) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + os.Exit(1) + } + filter := resourcediscovery.Filter{ + Labels: selector, + } if len(args) > 1 { filter.Name = args[1] } @@ -147,7 +179,14 @@ func runDescribe(cmd *cobra.Command, args []string, params *utils.CmdParams) { backendsPrinter.PrintDescribeView(resourceModel) case "namespace", "namespaces", "ns": - filter := resourcediscovery.Filter{} + selector, err := labels.Parse(labelSelector) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + os.Exit(1) + } + filter := resourcediscovery.Filter{ + Labels: selector, + } if len(args) > 1 { filter.Name = args[1] } diff --git a/gwctl/cmd/get.go b/gwctl/cmd/get.go index 330356ccde..75fe481871 100644 --- a/gwctl/cmd/get.go +++ b/gwctl/cmd/get.go @@ -46,7 +46,7 @@ func NewGetCommand() *cobra.Command { } cmd.Flags().StringVarP(&namespaceFlag, "namespace", "n", "default", "") cmd.Flags().BoolVarP(&allNamespacesFlag, "all-namespaces", "A", false, "If present, list requested resources from all namespaces.") - cmd.Flags().StringVarP(&labelSelector, "selector", "l", "", "Label selector.") + cmd.Flags().StringVarP(&labelSelector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.") return cmd } diff --git a/gwctl/pkg/printer/gatewayclasses_test.go b/gwctl/pkg/printer/gatewayclasses_test.go index fbf52abec1..cc06e6dc30 100644 --- a/gwctl/pkg/printer/gatewayclasses_test.go +++ b/gwctl/pkg/printer/gatewayclasses_test.go @@ -26,7 +26,6 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" testingclock "k8s.io/utils/clock/testing" @@ -235,64 +234,3 @@ ControllerName: example.net/gateway-controller }) } } - -// TestGatewayClassesPrinter_LabelSelector Tests label selector filtering for GatewayClasses in 'get' command. -func TestGatewayClassesPrinter_LabelSelector(t *testing.T) { - fakeClock := testingclock.NewFakeClock(time.Now()) - - gatewayClass := func(name string, labels map[string]string) *gatewayv1.GatewayClass { - return &gatewayv1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: labels, - CreationTimestamp: metav1.Time{ - Time: fakeClock.Now().Add(-365 * 24 * time.Hour), - }, - }, - Spec: gatewayv1.GatewayClassSpec{ - ControllerName: gatewayv1.GatewayController(name + "/controller"), - }, - Status: gatewayv1.GatewayClassStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }, - }, - } - } - objects := []runtime.Object{ - gatewayClass("foo-com-external-gateway-class", map[string]string{"app": "foo"}), - gatewayClass("foo-com-internal-gateway-class", map[string]string{"app": "foo", "env": "internal"}), - } - params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) - discoverer := resourcediscovery.Discoverer{ - K8sClients: params.K8sClients, - PolicyManager: params.PolicyManager, - } - labelSelector := "env=internal" - selector, err := labels.Parse(labelSelector) - if err != nil { - t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) - } - resourceModel, err := discoverer.DiscoverResourcesForGatewayClass(resourcediscovery.Filter{Labels: selector}) - if err != nil { - t.Fatalf("Failed to construct resourceModel: %v", resourceModel) - } - - gcp := &GatewayClassesPrinter{ - Out: params.Out, - Clock: fakeClock, - } - gcp.Print(resourceModel) - - got := params.Out.(*bytes.Buffer).String() - want := ` -NAME CONTROLLER ACCEPTED AGE -foo-com-internal-gateway-class foo-com-internal-gateway-class/controller True 365d -` - 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 8d1eed9f3f..d34f106e4d 100644 --- a/gwctl/pkg/printer/gateways_test.go +++ b/gwctl/pkg/printer/gateways_test.go @@ -22,10 +22,10 @@ import ( "time" "github.com/google/go-cmp/cmp" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" testingclock "k8s.io/utils/clock/testing" @@ -365,87 +365,3 @@ EffectivePolicies: t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } - -// TestGatewaysPrinter_LabelSelector tests label selector filtering for Gateways in 'get' command. -func TestGatewaysPrinter_LabelSelector(t *testing.T) { - fakeClock := testingclock.NewFakeClock(time.Now()) - gateway := func(name string, labels map[string]string) *gatewayv1.Gateway { - return &gatewayv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: labels, - CreationTimestamp: metav1.Time{ - Time: fakeClock.Now().Add(-5 * 24 * time.Hour), - }, - }, - 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"), - }, - }, - gateway("gateway-1", map[string]string{"app": "foo"}), - gateway("gateway-2", map[string]string{"app": "foo", "env": "internal"}), - } - - params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) - discoverer := resourcediscovery.Discoverer{ - K8sClients: params.K8sClients, - PolicyManager: params.PolicyManager, - } - labelSelector := "env=internal" - selector, err := labels.Parse(labelSelector) - if err != nil { - t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) - } - resourceModel, err := discoverer.DiscoverResourcesForGateway(resourcediscovery.Filter{Labels: selector}) - if err != nil { - t.Fatalf("Failed to construct resourceModel: %v", resourceModel) - } - - gp := &GatewaysPrinter{ - Out: params.Out, - Clock: fakeClock, - } - gp.Print(resourceModel) - - got := params.Out.(*bytes.Buffer).String() - want := ` -NAME CLASS ADDRESSES PORTS PROGRAMMED AGE -gateway-2 gatewayclass-1 192.168.100.5 8080 False 5d -` - - 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 be1397a0f7..e35ced4966 100644 --- a/gwctl/pkg/printer/httproutes_test.go +++ b/gwctl/pkg/printer/httproutes_test.go @@ -21,18 +21,17 @@ import ( "testing" "time" - gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" testingclock "k8s.io/utils/clock/testing" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "sigs.k8s.io/gateway-api/gwctl/pkg/common" "sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery" "sigs.k8s.io/gateway-api/gwctl/pkg/utils" @@ -436,86 +435,3 @@ EffectivePolicies: t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } - -// TestHTTPRoutesPrinter_LabelSelector tests label selector filtering for HTTPRoute in 'get' command. -func TestHTTPRoutesPrinter_LabelSelector(t *testing.T) { - fakeClock := testingclock.NewFakeClock(time.Now()) - httpRoute := func(name string, labels map[string]string) *gatewayv1.HTTPRoute { - return &gatewayv1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - CreationTimestamp: metav1.Time{ - Time: fakeClock.Now().Add(-24 * time.Hour), - }, - Labels: labels, - }, - Spec: gatewayv1.HTTPRouteSpec{ - Hostnames: []gatewayv1.Hostname{"example.com"}, - CommonRouteSpec: gatewayv1.CommonRouteSpec{ - ParentRefs: []gatewayv1.ParentReference{ - { - Name: "gateway-1", - }, - }, - }, - }, - } - } - - 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", - }, - }, - httpRoute("httproute-1", map[string]string{"app": "foo"}), - httpRoute("httproute-2", map[string]string{"app": "foo", "env": "internal"}), - } - - params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) - discoverer := resourcediscovery.Discoverer{ - K8sClients: params.K8sClients, - PolicyManager: params.PolicyManager, - } - - labelSelector := "env=internal" - selector, err := labels.Parse(labelSelector) - if err != nil { - t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) - } - resourceModel, err := discoverer.DiscoverResourcesForHTTPRoute(resourcediscovery.Filter{Labels: selector}) - if err != nil { - t.Fatalf("Failed to discover resources: %v", err) - } - - hp := &HTTPRoutesPrinter{ - Out: params.Out, - Clock: fakeClock, - } - hp.Print(resourceModel) - - got := params.Out.(*bytes.Buffer).String() - want := ` -NAMESPACE NAME HOSTNAMES PARENT REFS AGE -default httproute-2 example.com 1 24h - -` - 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 16681c6ffa..6e8d7e56ee 100644 --- a/gwctl/pkg/printer/namespace_test.go +++ b/gwctl/pkg/printer/namespace_test.go @@ -27,7 +27,6 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" testingclock "k8s.io/utils/clock/testing" @@ -258,58 +257,3 @@ DirectlyAttachedPolicies: t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) } } - -// TestNamespacesPrinter_LabelSelector tests label selector filtering for Namespaces in 'get' command. -func TestNamespacesPrinter_LabelSelector(t *testing.T) { - fakeClock := testingclock.NewFakeClock(time.Now()) - namespace := func(name string, labels map[string]string) *corev1.Namespace { - return &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - CreationTimestamp: metav1.Time{ - Time: fakeClock.Now().Add(-46 * 24 * time.Hour), - }, - Labels: labels, - }, - Status: corev1.NamespaceStatus{ - Phase: corev1.NamespaceActive, - }, - } - } - - objects := []runtime.Object{ - namespace("namespace-1", map[string]string{"app": "foo"}), - namespace("namespace-2", map[string]string{"app": "foo", "env": "internal"}), - } - - params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) - discoverer := resourcediscovery.Discoverer{ - K8sClients: params.K8sClients, - PolicyManager: params.PolicyManager, - } - labelSelector := "env=internal" - selector, err := labels.Parse(labelSelector) - if err != nil { - t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) - } - resourceModel, err := discoverer.DiscoverResourcesForNamespace(resourcediscovery.Filter{Labels: selector}) - if err != nil { - t.Fatalf("Failed to construct resourceModel: %v", resourceModel) - } - - nsp := &NamespacesPrinter{ - Out: params.Out, - Clock: fakeClock, - } - nsp.Print(resourceModel) - - got := params.Out.(*bytes.Buffer).String() - want := ` -NAME STATUS AGE -namespace-2 Active 46d -` - - 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/resourcediscovery/discoverer_test.go b/gwctl/pkg/resourcediscovery/discoverer_test.go new file mode 100644 index 0000000000..8499092390 --- /dev/null +++ b/gwctl/pkg/resourcediscovery/discoverer_test.go @@ -0,0 +1,293 @@ +/* +Copyright 2024 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 resourcediscovery + +import ( + corev1 "k8s.io/api/core/v1" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + testingclock "k8s.io/utils/clock/testing" + + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/gwctl/pkg/common" + "sigs.k8s.io/gateway-api/gwctl/pkg/utils" +) + +// TestDiscoverResourcesForGatewayClass_LabelSelector Tests label selector filtering for GatewayClasses. +func TestDiscoverResourcesForGatewayClass_LabelSelector(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + + gatewayClass := func(name string, labels map[string]string) *gatewayv1.GatewayClass { + return &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: labels, + CreationTimestamp: metav1.Time{ + Time: fakeClock.Now().Add(-365 * 24 * time.Hour), + }, + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(name + "/controller"), + }, + Status: gatewayv1.GatewayClassStatus{ + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }, + }, + } + } + objects := []runtime.Object{ + gatewayClass("foo-com-external-gateway-class", map[string]string{"app": "foo"}), + gatewayClass("foo-com-internal-gateway-class", map[string]string{"app": "foo", "env": "internal"}), + } + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + labelSelector := "env=internal" + selector, err := labels.Parse(labelSelector) + if err != nil { + t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + } + resourceModel, err := discoverer.DiscoverResourcesForGatewayClass(Filter{Labels: selector}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + expectedGatewayClassNames := []string{"foo-com-internal-gateway-class"} + gatewayClassNames := make([]string, 0, len(resourceModel.GatewayClasses)) + for _, gatewayClassNode := range resourceModel.GatewayClasses { + gatewayClassNames = append(gatewayClassNames, gatewayClassNode.GatewayClass.GetName()) + } + if diff := cmp.Diff(expectedGatewayClassNames, gatewayClassNames); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", gatewayClassNames, expectedGatewayClassNames, diff) + } +} + +// TestDiscoverResourcesForGateway_LabelSelector tests label selector filtering for Gateways. +func TestDiscoverResourcesForGateway_LabelSelector(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + gateway := func(name string, labels map[string]string) *gatewayv1.Gateway { + return &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: labels, + CreationTimestamp: metav1.Time{ + Time: fakeClock.Now().Add(-5 * 24 * time.Hour), + }, + }, + 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"), + }, + }, + gateway("gateway-1", map[string]string{"app": "foo"}), + gateway("gateway-2", map[string]string{"app": "foo", "env": "internal"}), + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + labelSelector := "env=internal" + selector, err := labels.Parse(labelSelector) + if err != nil { + t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + } + resourceModel, err := discoverer.DiscoverResourcesForGateway(Filter{Labels: selector}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + expectedGatewayNames := []string{"gateway-2"} + gatewayNames := make([]string, 0, len(resourceModel.Gateways)) + for _, gatewayNode := range resourceModel.Gateways { + gatewayNames = append(gatewayNames, gatewayNode.Gateway.GetName()) + } + + if diff := cmp.Diff(expectedGatewayNames, gatewayNames); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", gatewayNames, expectedGatewayNames, diff) + } +} + +// TestDiscoverResourcesForHTTPRoute_LabelSelector tests label selector filtering for HTTPRoute. +func TestDiscoverResourcesForHTTPRoute_LabelSelector(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + httpRoute := func(name string, labels map[string]string) *gatewayv1.HTTPRoute { + return &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + CreationTimestamp: metav1.Time{ + Time: fakeClock.Now().Add(-24 * time.Hour), + }, + Labels: labels, + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"example.com"}, + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{ + { + Name: "gateway-1", + }, + }, + }, + }, + } + } + + 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", + }, + }, + httpRoute("httproute-1", map[string]string{"app": "foo"}), + httpRoute("httproute-2", map[string]string{"app": "foo", "env": "internal"}), + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + + labelSelector := "env=internal" + selector, err := labels.Parse(labelSelector) + if err != nil { + t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + } + resourceModel, err := discoverer.DiscoverResourcesForHTTPRoute(Filter{Labels: selector}) + if err != nil { + t.Fatalf("Failed to discover resources: %v", err) + } + + expectedHTTPRouteNames := []string{"httproute-2"} + HTTPRouteNames := make([]string, 0, len(resourceModel.HTTPRoutes)) + for _, HTTPRouteNode := range resourceModel.HTTPRoutes { + HTTPRouteNames = append(HTTPRouteNames, HTTPRouteNode.HTTPRoute.GetName()) + } + + if diff := cmp.Diff(expectedHTTPRouteNames, HTTPRouteNames); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", expectedHTTPRouteNames, HTTPRouteNames, diff) + } +} + +// TestDiscoverResourcesForNamespace_LabelSelector tests label selector filtering for Namespaces. +func TestDiscoverResourcesForNamespace_LabelSelector(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + namespace := func(name string, labels map[string]string) *corev1.Namespace { + return &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + CreationTimestamp: metav1.Time{ + Time: fakeClock.Now().Add(-46 * 24 * time.Hour), + }, + Labels: labels, + }, + Status: corev1.NamespaceStatus{ + Phase: corev1.NamespaceActive, + }, + } + } + + objects := []runtime.Object{ + namespace("namespace-1", map[string]string{"app": "foo"}), + namespace("namespace-2", map[string]string{"app": "foo", "env": "internal"}), + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + labelSelector := "env=internal" + selector, err := labels.Parse(labelSelector) + if err != nil { + t.Errorf("Unable to find resources that match the label selector \"%s\": %v\n", labelSelector, err) + } + resourceModel, err := discoverer.DiscoverResourcesForNamespace(Filter{Labels: selector}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + expectedNamespaceNames := []string{"namespace-2"} + namespaceNames := make([]string, 0, len(resourceModel.Namespaces)) + for _, namespaceNode := range resourceModel.Namespaces { + namespaceNames = append(namespaceNames, namespaceNode.Namespace.Name) + } + + if diff := cmp.Diff(expectedNamespaceNames, namespaceNames); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", expectedNamespaceNames, namespaceNames, diff) + } +}