-
Notifications
You must be signed in to change notification settings - Fork 493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add label filtering (-l) for describe commands #2934
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
} | ||
Comment on lines
+129
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
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) | ||
} | ||
Comment on lines
+182
to
+186
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
filter := resourcediscovery.Filter{ | ||
Labels: selector, | ||
} | ||
if len(args) > 1 { | ||
filter.Name = args[1] | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -296,3 +296,102 @@ 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) | ||
} | ||
} | ||
|
||
// TestGatewayClassesPrinterDescribe_LabelSelector Tests label selector filtering for GatewayClasses in 'describe' command. | ||
func TestGatewayClassesPrinterDescribe_LabelSelector(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the thorough tests @yeedove! I was actually thinking about this part and I feel that maybe testing LabelSelector is something which is better handled through unit tests within The problem with LabelSelector tests right now is that they aren't just testing LabelSelector but are also testing how exactly the entire GatewayClass object gets printed (which is something that is already covered by the normal test functions like (I know that tests like Within Additionally, we could then also get rid of the LabelSelector tests for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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"), | ||
Description: common.PtrTo("random"), | ||
}, | ||
Status: gatewayv1.GatewayClassStatus{ | ||
Conditions: []metav1.Condition{ | ||
{ | ||
Type: "Accepted", | ||
Status: metav1.ConditionTrue, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
objects := []runtime.Object{ | ||
&apiextensionsv1.CustomResourceDefinition{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "healthcheckpolicies.foo.com", | ||
Labels: map[string]string{ | ||
gatewayv1alpha2.PolicyLabelKey: "true", | ||
}, | ||
}, | ||
Spec: apiextensionsv1.CustomResourceDefinitionSpec{ | ||
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": "policy-name", | ||
}, | ||
"spec": map[string]interface{}{ | ||
"targetRef": map[string]interface{}{ | ||
"group": "gateway.networking.k8s.io", | ||
"kind": "GatewayClass", | ||
"name": "foo-com-internal-gateway-class", | ||
}, | ||
}, | ||
}, | ||
}, | ||
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.PrintDescribeView(resourceModel) | ||
|
||
got := params.Out.(*bytes.Buffer).String() | ||
want := ` | ||
Name: foo-com-internal-gateway-class | ||
ControllerName: foo-com-internal-gateway-class/controller | ||
Description: random | ||
DirectlyAttachedPolicies: | ||
- Group: foo.com | ||
Kind: HealthCheckPolicy | ||
Name: policy-name | ||
` | ||
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) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this entire section can be taken outside the
switch
(say at Line 90) to reduce this code's repeatability across all thecase
-esThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If placed on line 90, gwctl will first check the label and then the resources. I think the label logic in
gwctl
should align withkubectl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get your point though I'm not sure if that's worthwhile.
As a user, I don't care (I never even noticed haha) the order of this validation (resource_type -> label OR label -> resource_type)
As a developer, I "personally" wouldn't find this reason strong enough to have this level of code repeatability across so many cases.
Moreover, this would add up to the cognitive load of copy-ing the same code again in case in the future a new set of resource types are introduced under gwctl.
Rest, this is just my personal pov.
I'd leave this to yours and @gauravkghildiyal 's judgement :)
Cheers!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep we do have code-duplication in a couple of places at present that needs to be improved. (Sorry I may be responsible for many of those) @yeedove thanks for an eye towards such subtle details, that's really nice!
Is it not feasible to have a function like
parseLabelSelectorOrExit()
and still keep the error evaluation order as a middle ground? If that's not possible, I feel like it's fair game if we do get rid of the code duplication here as that may not affect the user experience drastically. (Thanks @yashvardhan-kukreja!)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the change Dennis!
Yes you are right that the label selection (for the time being) is only for the immediate resource being fetched (like
gwctl describe httproutes -l mylabel=myhttproute
is only supposed to filter the HTTPRoutes, and not extend the filtering to resources associated with the HTTPRoute, like Gateways and Backends)From your present commit, the function under test for the tests happens to be
DiscoverResourcesForGatewayClass()
(and equivalent). Given this, can we please move them to thediscoverer_test.go
file within theresourcediscovery
package?