Skip to content

Commit 312a334

Browse files
Add CEL validation for GRPCMethodMatch
1 parent a975fd6 commit 312a334

File tree

3 files changed

+138
-10
lines changed

3 files changed

+138
-10
lines changed

apis/v1alpha2/grpcroute_types.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ type GRPCRouteSpec struct {
147147
//
148148
// +optional
149149
// +kubebuilder:validation:MaxItems=16
150-
// +kubebuilder:default={{matches: {{method: {type: "Exact"}}}}}
151150
Rules []GRPCRouteRule `json:"rules,omitempty"`
152151
}
153152

@@ -313,6 +312,10 @@ type GRPCRouteMatch struct {
313312
// request service and/or method.
314313
//
315314
// At least one of Service and Method MUST be a non-empty string.
315+
//
316+
// +kubebuilder:validation:XValidation:message="One or both of 'service' or 'method' must be specified",rule="has(self.type) ? has(self.service) || has(self.method) : true"
317+
// +kubebuilder:validation:XValidation:message="service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)",rule="(!has(self.type) || self.type == 'Exact') && has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"): true"
318+
// +kubebuilder:validation:XValidation:message="method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)",rule="(!has(self.type) || self.type == 'Exact') && has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"): true"
316319
type GRPCMethodMatch struct {
317320
// Type specifies how to match against the service and/or method.
318321
// Support: Core (Exact with service and method specified)

config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml

Lines changed: 17 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/test/cel/grpcroute_test.go

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ package main
2222
import (
2323
"context"
2424
"fmt"
25-
"testing"
2625
"strings"
26+
"testing"
2727
"time"
28+
2829
gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
2930

3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -175,7 +176,7 @@ func TestGRPCRouteRule(t *testing.T) {
175176
Matches: []gatewayv1a2.GRPCRouteMatch{
176177
{
177178
Method: &gatewayv1a2.GRPCMethodMatch{
178-
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
179+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
179180
Service: ptrTo("helloworld.Greeter"),
180181
},
181182
},
@@ -201,7 +202,7 @@ func TestGRPCRouteRule(t *testing.T) {
201202
Matches: []gatewayv1a2.GRPCRouteMatch{
202203
{
203204
Method: &gatewayv1a2.GRPCMethodMatch{
204-
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
205+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
205206
Method: ptrTo("SayHello"),
206207
},
207208
},
@@ -228,7 +229,7 @@ func TestGRPCRouteRule(t *testing.T) {
228229
Matches: []gatewayv1a2.GRPCRouteMatch{
229230
{
230231
Method: &gatewayv1a2.GRPCMethodMatch{
231-
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
232+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
232233
Service: ptrTo("helloworld.Greeter"),
233234
},
234235
},
@@ -297,6 +298,118 @@ func TestGRPCRouteRule(t *testing.T) {
297298
}
298299
}
299300

301+
func TestGRPCMethodMatch(t *testing.T) {
302+
service := "foo.Test.Example"
303+
method := "Login"
304+
regex := ".*"
305+
306+
tests := []struct {
307+
name string
308+
method gatewayv1a2.GRPCMethodMatch
309+
wantErrors []string
310+
}{
311+
{
312+
name: "valid GRPCRoute with 1 service in GRPCMethodMatch field",
313+
method: gatewayv1a2.GRPCMethodMatch{
314+
Service: &service,
315+
},
316+
},
317+
{
318+
name: "valid GRPCRoute with 1 method in GRPCMethodMatch field",
319+
method: gatewayv1a2.GRPCMethodMatch{
320+
Method: &method,
321+
},
322+
},
323+
{
324+
name: "invalid GRPCRoute missing service or method in GRPCMethodMatch field",
325+
method: gatewayv1a2.GRPCMethodMatch{
326+
Service: nil,
327+
Method: nil,
328+
},
329+
wantErrors: []string{"One or both of 'service' or 'method"},
330+
},
331+
{
332+
name: "GRPCRoute use regex in service and method with undefined match type",
333+
method: gatewayv1a2.GRPCMethodMatch{
334+
Service: &regex,
335+
Method: &regex,
336+
},
337+
wantErrors: []string{"service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)", "method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)"},
338+
},
339+
{
340+
name: "GRPCRoute use regex in service and method with match type Exact",
341+
method: gatewayv1a2.GRPCMethodMatch{
342+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
343+
Service: &regex,
344+
Method: &regex,
345+
},
346+
wantErrors: []string{"service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)", "method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)"},
347+
},
348+
{
349+
name: "GRPCRoute use regex in method with undefined match type",
350+
method: gatewayv1a2.GRPCMethodMatch{
351+
Method: &regex,
352+
},
353+
wantErrors: []string{"method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)"},
354+
},
355+
{
356+
name: "GRPCRoute use regex in service with match type Exact",
357+
method: gatewayv1a2.GRPCMethodMatch{
358+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
359+
Service: &regex,
360+
},
361+
wantErrors: []string{"service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)"},
362+
},
363+
{
364+
name: "GRPCRoute use regex in service and method with match type RegularExpression",
365+
method: gatewayv1a2.GRPCMethodMatch{
366+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchRegularExpression),
367+
Service: &regex,
368+
Method: &regex,
369+
},
370+
},
371+
{
372+
name: "GRPCRoute use valid service and method with undefined match type",
373+
method: gatewayv1a2.GRPCMethodMatch{
374+
Service: &service,
375+
Method: &method,
376+
},
377+
},
378+
{
379+
name: "GRPCRoute use valid service and method with match type Exact",
380+
method: gatewayv1a2.GRPCMethodMatch{
381+
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
382+
Service: &service,
383+
Method: &method,
384+
},
385+
},
386+
}
387+
388+
for _, tc := range tests {
389+
tc := tc
390+
t.Run(tc.name, func(t *testing.T) {
391+
route := gatewayv1a2.GRPCRoute{
392+
ObjectMeta: metav1.ObjectMeta{
393+
Name: fmt.Sprintf("foo-%v", time.Now().UnixNano()),
394+
Namespace: metav1.NamespaceDefault,
395+
},
396+
Spec: gatewayv1a2.GRPCRouteSpec{
397+
Rules: []gatewayv1a2.GRPCRouteRule{
398+
{
399+
Matches: []gatewayv1a2.GRPCRouteMatch{
400+
{
401+
Method: &tc.method,
402+
},
403+
},
404+
},
405+
},
406+
},
407+
}
408+
validateGRPCRoute(t, &route, tc.wantErrors)
409+
})
410+
}
411+
}
412+
300413
func validateGRPCRoute(t *testing.T, route *gatewayv1a2.GRPCRoute, wantErrors []string) {
301414
t.Helper()
302415

0 commit comments

Comments
 (0)