Skip to content

Commit 800c57c

Browse files
committed
fix: use size(self.name) > 0 in CEL rule to avoid YAML quote issues
Replace self.name != '' with size(self.name) > 0 in the XValidation rule. The trailing '' in a YAML plain scalar was being mangled by kustomize during make install, producing an invalid rule. Using size() avoids any string-literal quoting at line end. Update test to walk the parsed YAML structure instead of raw substring match.
1 parent 64ffc78 commit 800c57c

3 files changed

Lines changed: 46 additions & 7 deletions

File tree

api/v2/apisixroute_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ type ApisixRouteAuthenticationLDAPAuth struct {
412412
}
413413

414414
// ApisixRouteHTTPMatchExprSubject describes the subject of a route matching expression.
415-
// +kubebuilder:validation:XValidation:rule="self.scope == 'Path' || self.name != ''",message="name is required when scope is not Path"
415+
// +kubebuilder:validation:XValidation:rule="self.scope == 'Path' || size(self.name) > 0",message="name is required when scope is not Path"
416416
type ApisixRouteHTTPMatchExprSubject struct {
417417
// Scope specifies the subject scope.
418418
// Supported values: `Header`, `Query`, `Path`, `Cookie`, `Variable`, `Body`.

api/v2/apisixroute_types_test.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func strPtr(s string) *string { return &s }
3131

3232
// celSubjectRule is the CEL expression used in the +kubebuilder:validation:XValidation
3333
// marker on ApisixRouteHTTPMatchExprSubject.
34-
const celSubjectRule = `self.scope == 'Path' || self.name != ''`
34+
const celSubjectRule = `self.scope == 'Path' || size(self.name) > 0`
3535

3636
func evalCELSubjectRule(t *testing.T, scope, name string) bool {
3737
t.Helper()
@@ -74,15 +74,53 @@ func TestCEL_SubjectRule_InCRD(t *testing.T) {
7474
var crd map[string]any
7575
require.NoError(t, yaml.Unmarshal(data, &crd))
7676

77+
// Ensure no typographic/smart quotes crept in anywhere in the file.
7778
raw := string(data)
78-
// The CEL rule must appear with ASCII single-quotes only.
79-
assert.Contains(t, raw, `self.scope == 'Path' || self.name != ''`,
80-
"CRD should contain the XValidation rule with ASCII quotes")
81-
// Ensure no typographic/smart quotes crept in.
8279
assert.NotContains(t, raw, "\u2018", "CRD must not contain left single quotation mark \u2018")
8380
assert.NotContains(t, raw, "\u2019", "CRD must not contain right single quotation mark \u2019")
8481
assert.NotContains(t, raw, "\u201c", "CRD must not contain left double quotation mark \u201c")
8582
assert.NotContains(t, raw, "\u201d", "CRD must not contain right double quotation mark \u201d")
83+
84+
// Walk the parsed CRD to extract the x-kubernetes-validations rule string directly,
85+
// which is more robust than substring matching against the raw YAML (line-wrapping safe).
86+
rule := extractXValidationRule(t, crd)
87+
assert.Equal(t, celSubjectRule, rule,
88+
"XValidation rule in CRD must match the expected CEL expression")
89+
}
90+
91+
// extractXValidationRule walks the parsed CRD map to find the first
92+
// x-kubernetes-validations rule on the subject property of HTTP match exprs.
93+
func extractXValidationRule(t *testing.T, crd map[string]any) string {
94+
t.Helper()
95+
// Path: spec.versions[0].schema.openAPIV3Schema
96+
// .properties.spec.properties.http.items
97+
// .properties.match.properties.exprs.items
98+
// .properties.subject.x-kubernetes-validations[0].rule
99+
get := func(m map[string]any, key string) map[string]any {
100+
v, ok := m[key]
101+
require.True(t, ok, "key %q not found", key)
102+
mv, ok := v.(map[string]any)
103+
require.True(t, ok, "key %q is not a map", key)
104+
return mv
105+
}
106+
spec := get(crd, "spec")
107+
versions := spec["versions"].([]any)
108+
require.NotEmpty(t, versions)
109+
schema := get(versions[0].(map[string]any), "schema")
110+
root := get(schema, "openAPIV3Schema")
111+
props := get(root, "properties")
112+
specProps := get(get(props, "spec"), "properties")
113+
httpItems := get(get(specProps, "http"), "items")
114+
matchProps := get(get(get(httpItems, "properties"), "match"), "properties")
115+
exprsItems := get(get(matchProps, "exprs"), "items")
116+
subject := get(get(exprsItems, "properties"), "subject")
117+
validations, ok := subject["x-kubernetes-validations"].([]any)
118+
require.True(t, ok, "x-kubernetes-validations not found or not a list")
119+
require.NotEmpty(t, validations)
120+
first := validations[0].(map[string]any)
121+
rule, ok := first["rule"].(string)
122+
require.True(t, ok, "rule field not found or not a string")
123+
return rule
86124
}
87125

88126
func TestToVars_ScopeBody_SimpleField(t *testing.T) {

config/crd/bases/apisix.apache.org_apisixroutes.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ spec:
232232
type: object
233233
x-kubernetes-validations:
234234
- message: name is required when scope is not Path
235-
rule: self.scope == 'Path' || self.name != ''
235+
rule: self.scope == 'Path' || size(self.name) >
236+
0
236237
value:
237238
description: |-
238239
Value defines a single value to compare against the subject.

0 commit comments

Comments
 (0)