Skip to content

Commit e92864e

Browse files
add ut
Signed-off-by: Yaroslav Borbat <[email protected]>
1 parent 6e975fe commit e92864e

File tree

5 files changed

+258
-8
lines changed

5 files changed

+258
-8
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/gobuffalo/flect v1.0.3
88
github.com/google/go-cmp v0.7.0
99
github.com/onsi/ginkgo v1.16.5
10+
github.com/onsi/ginkgo/v2 v2.23.3
1011
github.com/onsi/gomega v1.37.0
1112
github.com/spf13/cobra v1.9.1
1213
github.com/spf13/pflag v1.0.6
@@ -29,8 +30,10 @@ require (
2930
github.com/go-openapi/jsonpointer v0.21.0 // indirect
3031
github.com/go-openapi/jsonreference v0.20.2 // indirect
3132
github.com/go-openapi/swag v0.23.0 // indirect
33+
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
3234
github.com/gogo/protobuf v1.3.2 // indirect
3335
github.com/google/gnostic-models v0.6.9 // indirect
36+
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
3437
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3538
github.com/josharian/intern v1.0.0 // indirect
3639
github.com/json-iterator/go v1.1.12 // indirect

go.sum

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
2121
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
2222
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
2323
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
24-
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
2524
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
2625
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
2726
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=

pkg/crd/markers/crd.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ type SchemaModifier struct {
484484
}
485485

486486
func (s SchemaModifier) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, _ string) error {
487-
ruleRegex, err := s.parsePattern()
487+
ruleRegex, err := s.ParsePattern()
488488
if err != nil {
489489
return fmt.Errorf("failed to parse rule: %w", err)
490490
}
@@ -595,12 +595,8 @@ func (s SchemaModifier) applyToSchema(schema *apiext.JSONSchemaProps) {
595595
}
596596
}
597597

598-
func (s SchemaModifier) parsePattern() (*regexp.Regexp, error) {
599-
pattern := s.PathPattern
600-
pattern = strings.ReplaceAll(pattern, "**", "!☸!")
601-
pattern = strings.ReplaceAll(pattern, "*", "[^/]+")
602-
pattern = strings.ReplaceAll(pattern, "!☸!", ".*")
603-
598+
func (s SchemaModifier) ParsePattern() (*regexp.Regexp, error) {
599+
pattern := strings.NewReplacer("**", ".*", "*", "[^/]+").Replace(s.PathPattern)
604600
regexStr := "^" + pattern + "$"
605601

606602
compiledRegex, err := regexp.Compile(regexStr)

pkg/crd/markers/crd_test.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package markers_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
7+
"k8s.io/utils/ptr"
8+
9+
"sigs.k8s.io/controller-tools/pkg/crd/markers"
10+
)
11+
12+
var _ = Describe("SchemaModifierMarker", func() {
13+
baseCRD := func() *apiext.CustomResourceDefinitionSpec {
14+
return &apiext.CustomResourceDefinitionSpec{
15+
Versions: []apiext.CustomResourceDefinitionVersion{
16+
{
17+
Name: "v1",
18+
Schema: &apiext.CustomResourceValidation{
19+
OpenAPIV3Schema: &apiext.JSONSchemaProps{
20+
Properties: map[string]apiext.JSONSchemaProps{
21+
"spec": {
22+
Properties: map[string]apiext.JSONSchemaProps{},
23+
},
24+
},
25+
},
26+
},
27+
},
28+
},
29+
}
30+
}
31+
addToSpec := func(crd *apiext.CustomResourceDefinitionSpec, key string, props apiext.JSONSchemaProps) {
32+
crd.Versions[0].Schema.OpenAPIV3Schema.Properties["spec"].Properties[key] = props
33+
}
34+
35+
Context("Pattern matching", func() {
36+
const (
37+
descOrig = "original"
38+
descExpected = "modified"
39+
)
40+
barProps := func(desc string) map[string]apiext.JSONSchemaProps {
41+
return map[string]apiext.JSONSchemaProps{
42+
"foo": {
43+
Description: desc,
44+
Properties: map[string]apiext.JSONSchemaProps{
45+
"bar": {
46+
Description: desc,
47+
},
48+
},
49+
},
50+
"baz": {
51+
Description: desc,
52+
},
53+
}
54+
}
55+
56+
It("should match only direct children with pattern /*", func() {
57+
crdOrig := baseCRD()
58+
crdExpected := baseCRD()
59+
60+
addToSpec(crdOrig, "foo", apiext.JSONSchemaProps{Description: descOrig})
61+
addToSpec(crdOrig, "bar", apiext.JSONSchemaProps{
62+
Description: descOrig,
63+
Properties: barProps(descOrig),
64+
})
65+
66+
addToSpec(crdExpected, "foo", apiext.JSONSchemaProps{Description: descExpected})
67+
addToSpec(crdExpected, "bar", apiext.JSONSchemaProps{
68+
Description: descExpected,
69+
Properties: barProps(descOrig),
70+
})
71+
72+
marker := &markers.SchemaModifier{
73+
PathPattern: "/spec/*",
74+
Description: ptr.To(descExpected),
75+
}
76+
77+
Expect(marker.ApplyToCRD(crdOrig, "v1")).To(Succeed())
78+
Expect(crdOrig).To(Equal(crdExpected))
79+
})
80+
81+
It("should match deep nested fields with /**", func() {
82+
crdOrig := baseCRD()
83+
crdExpected := baseCRD()
84+
85+
addToSpec(crdOrig, "foo", apiext.JSONSchemaProps{Description: descOrig})
86+
addToSpec(crdOrig, "bar", apiext.JSONSchemaProps{
87+
Description: descOrig,
88+
Properties: barProps(descOrig),
89+
})
90+
91+
addToSpec(crdExpected, "foo", apiext.JSONSchemaProps{Description: descExpected})
92+
addToSpec(crdExpected, "bar", apiext.JSONSchemaProps{
93+
Description: descExpected,
94+
Properties: barProps(descExpected),
95+
})
96+
97+
marker := &markers.SchemaModifier{
98+
PathPattern: "/spec/**",
99+
Description: ptr.To(descExpected),
100+
}
101+
102+
Expect(marker.ApplyToCRD(crdOrig, "v1")).To(Succeed())
103+
Expect(crdOrig).To(Equal(crdExpected))
104+
})
105+
106+
It("should return error on invalid path pattern", func() {
107+
crd := baseCRD()
108+
addToSpec(crd, "foo", apiext.JSONSchemaProps{Description: descOrig})
109+
110+
marker := &markers.SchemaModifier{
111+
PathPattern: "[invalid-regex",
112+
}
113+
114+
err := marker.ApplyToCRD(crd, "v1")
115+
Expect(err).To(HaveOccurred())
116+
})
117+
})
118+
119+
DescribeTable("Should modify crd /spec/foo", func(origFooProps, expectedFooProps apiext.JSONSchemaProps, marker *markers.SchemaModifier) {
120+
crdOrig := baseCRD()
121+
addToSpec(crdOrig, "foo", origFooProps)
122+
123+
crdExpected := baseCRD()
124+
addToSpec(crdExpected, "foo", expectedFooProps)
125+
126+
marker.PathPattern = "/spec/foo"
127+
Expect(marker.ApplyToCRD(crdOrig, "v1")).To(Succeed())
128+
Expect(crdOrig).To(Equal(crdExpected))
129+
},
130+
Entry("should trim description",
131+
apiext.JSONSchemaProps{Description: "foo"},
132+
apiext.JSONSchemaProps{Description: ""},
133+
&markers.SchemaModifier{Description: ptr.To("")},
134+
),
135+
Entry("should replace format",
136+
apiext.JSONSchemaProps{Format: "foo"},
137+
apiext.JSONSchemaProps{Format: "bar"},
138+
&markers.SchemaModifier{Format: ptr.To("bar")},
139+
),
140+
Entry("should replace maximum",
141+
apiext.JSONSchemaProps{Maximum: ptr.To(1.0)},
142+
apiext.JSONSchemaProps{Maximum: ptr.To(2.0)},
143+
&markers.SchemaModifier{Maximum: ptr.To(2.0)},
144+
),
145+
Entry("should replace exclusiveMaximum",
146+
apiext.JSONSchemaProps{ExclusiveMaximum: true},
147+
apiext.JSONSchemaProps{ExclusiveMaximum: false},
148+
&markers.SchemaModifier{ExclusiveMaximum: ptr.To(false)},
149+
),
150+
Entry("should replace minimum",
151+
apiext.JSONSchemaProps{Minimum: ptr.To(1.0)},
152+
apiext.JSONSchemaProps{Minimum: ptr.To(2.0)},
153+
&markers.SchemaModifier{Minimum: ptr.To(2.0)},
154+
),
155+
Entry("should replace exclusiveMinimum",
156+
apiext.JSONSchemaProps{ExclusiveMinimum: true},
157+
apiext.JSONSchemaProps{ExclusiveMinimum: false},
158+
&markers.SchemaModifier{ExclusiveMinimum: ptr.To(false)},
159+
),
160+
Entry("should replace maxLength",
161+
apiext.JSONSchemaProps{MaxLength: ptr.To[int64](1)},
162+
apiext.JSONSchemaProps{MaxLength: ptr.To[int64](2)},
163+
&markers.SchemaModifier{MaxLength: ptr.To(2)},
164+
),
165+
Entry("should replace minLength",
166+
apiext.JSONSchemaProps{MinLength: ptr.To[int64](1)},
167+
apiext.JSONSchemaProps{MinLength: ptr.To[int64](2)},
168+
&markers.SchemaModifier{MinLength: ptr.To(2)},
169+
),
170+
Entry("should replace pattern",
171+
apiext.JSONSchemaProps{Pattern: "foo"},
172+
apiext.JSONSchemaProps{Pattern: "bar"},
173+
&markers.SchemaModifier{Pattern: ptr.To("bar")},
174+
),
175+
Entry("should replace maxItems",
176+
apiext.JSONSchemaProps{MaxItems: ptr.To[int64](1)},
177+
apiext.JSONSchemaProps{MaxItems: ptr.To[int64](2)},
178+
&markers.SchemaModifier{MaxItems: ptr.To(2)},
179+
),
180+
Entry("should replace minItems",
181+
apiext.JSONSchemaProps{MinItems: ptr.To[int64](1)},
182+
apiext.JSONSchemaProps{MinItems: ptr.To[int64](2)},
183+
&markers.SchemaModifier{MinItems: ptr.To(2)},
184+
),
185+
Entry("should replace uniqueItems",
186+
apiext.JSONSchemaProps{UniqueItems: true},
187+
apiext.JSONSchemaProps{UniqueItems: false},
188+
&markers.SchemaModifier{UniqueItems: ptr.To(false)},
189+
),
190+
Entry("should replace multipleOf",
191+
apiext.JSONSchemaProps{MultipleOf: ptr.To(1.0)},
192+
apiext.JSONSchemaProps{MultipleOf: ptr.To(2.0)},
193+
&markers.SchemaModifier{MultipleOf: ptr.To(2.0)},
194+
),
195+
Entry("should replace maxProperties",
196+
apiext.JSONSchemaProps{MaxProperties: ptr.To[int64](1)},
197+
apiext.JSONSchemaProps{MaxProperties: ptr.To[int64](2)},
198+
&markers.SchemaModifier{MaxProperties: ptr.To(2)},
199+
),
200+
Entry("should replace minProperties",
201+
apiext.JSONSchemaProps{MinProperties: ptr.To[int64](1)},
202+
apiext.JSONSchemaProps{MinProperties: ptr.To[int64](2)},
203+
&markers.SchemaModifier{MinProperties: ptr.To(2)},
204+
),
205+
Entry("should replace required",
206+
apiext.JSONSchemaProps{Required: []string{"foo"}},
207+
apiext.JSONSchemaProps{Required: []string{"bar"}},
208+
&markers.SchemaModifier{Required: ptr.To([]string{"bar"})},
209+
),
210+
Entry("should replace nullable",
211+
apiext.JSONSchemaProps{Nullable: true},
212+
apiext.JSONSchemaProps{Nullable: false},
213+
&markers.SchemaModifier{Nullable: ptr.To(false)},
214+
),
215+
)
216+
217+
Context("Parse pattern", func() {
218+
It("should convert * to [^/]+", func() {
219+
sm := markers.SchemaModifier{PathPattern: "/spec/*"}
220+
re, err := sm.ParsePattern()
221+
Expect(err).NotTo(HaveOccurred())
222+
Expect(re.String()).To(Equal("^/spec/[^/]+$"))
223+
})
224+
225+
It("should convert ** to .*", func() {
226+
sm := markers.SchemaModifier{PathPattern: "/spec/**/field"}
227+
re, err := sm.ParsePattern()
228+
Expect(err).NotTo(HaveOccurred())
229+
Expect(re.String()).To(Equal("^/spec/.*/field$"))
230+
})
231+
232+
It("should fail with invalid rule", func() {
233+
sm := markers.SchemaModifier{PathPattern: "[invalid-regex"}
234+
_, err := sm.ParsePattern()
235+
Expect(err).To(HaveOccurred())
236+
Expect(err).To(MatchError(ContainSubstring("error parsing regexp")))
237+
})
238+
})
239+
})

pkg/crd/markers/markers_suite_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package markers_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestCRDMarkers(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "CRD Markers Suite")
13+
}

0 commit comments

Comments
 (0)