Skip to content

Commit 4752ed2

Browse files
authored
Merge pull request #323 from marun/crd-schema-defaulting
✨ Add a marker to support field defaulting in crd schema
2 parents 64b0534 + 29f3d48 commit 4752ed2

File tree

8 files changed

+122
-4
lines changed

8 files changed

+122
-4
lines changed

pkg/crd/markers/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ limitations under the License.
2121
//
2222
// Validation Markers
2323
//
24-
// Validation markers have values that imprlement ApplyToSchema
24+
// Validation markers have values that implement ApplyToSchema
2525
// (crd.SchemaMarker). Any marker implementing this will automatically
2626
// be run after the rest of a given schema node has been generated.
2727
// Markers that need to be run before any other markers can also

pkg/crd/markers/validation.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ var FieldOnlyMarkers = []*definitionWithHelp{
7272

7373
must(markers.MakeDefinition("nullable", markers.DescribesField, Nullable{})).
7474
WithHelp(Nullable{}.Help()),
75+
76+
must(markers.MakeAnyTypeDefinition("kubebuilder:validation:Default", markers.DescribesField, Default{})).
77+
WithHelp(Default{}.Help()),
7578
}
7679

7780
func init() {
@@ -159,6 +162,19 @@ type Type string
159162
// This is often not necessary, but may be helpful with custom serialization.
160163
type Nullable struct{}
161164

165+
// +controllertools:marker:generateHelp:category="CRD validation"
166+
// Default sets the default value for this field.
167+
//
168+
// A default value will be accepted as any value valid for the
169+
// field. Formatting for common types include: boolean: `true`, string:
170+
// `Cluster`, numerical: `1.24`, array: `{1,2}`, object: `{policy:
171+
// "delete"}`). Defaults should be defined in pruned form, and only best-effort
172+
// validation will be performed. Full validation of a default requires
173+
// submission of the containing CRD to an apiserver.
174+
type Default struct {
175+
Value interface{}
176+
}
177+
162178
func (m Maximum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error {
163179
if schema.Type != "integer" {
164180
return fmt.Errorf("must apply maximum to an integer")
@@ -284,3 +300,13 @@ func (m Nullable) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error {
284300
schema.Nullable = true
285301
return nil
286302
}
303+
304+
// Defaults are only valid CRDs created with the v1 API
305+
func (m Default) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error {
306+
marshalledDefault, err := json.Marshal(m.Value)
307+
if err != nil {
308+
return err
309+
}
310+
schema.Default = &v1beta1.JSON{Raw: marshalledDefault}
311+
return nil
312+
}

pkg/crd/markers/zz_generated.markerhelp.go

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

pkg/crd/testdata/cronjob_types.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ type CronJobSpec struct {
9393

9494
// This tests that markers that are allowed on both fields and types are applied to types
9595
TwoOfAKindPart1 LongerString `json:"twoOfAKindPart1"`
96+
97+
// This tests that primitive defaulting can be performed.
98+
// +kubebuilder:validation:Default=forty-two
99+
DefaultedString string `json:"defaultedString"`
100+
101+
// This tests that slice defaulting can be performed.
102+
// +kubebuilder:validation:Default={a,b}
103+
DefaultedSlice []string `json:"defaultedSlice"`
104+
105+
// This tests that object defaulting can be performed.
106+
// +kubebuilder:validation:Default={{nested: {foo: "baz", bar: true}},{nested: {bar: false}}}
107+
DefaultedObject []RootObject `json:"defaultedObject"`
108+
}
109+
110+
type NestedObject struct {
111+
Foo string `json:"foo"`
112+
Bar bool `json:"bar"`
113+
}
114+
115+
type RootObject struct {
116+
Nested NestedObject `json:"nested"`
96117
}
97118

98119
// +kubebuilder:validation:MinLength=4

pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,42 @@ spec:
5959
- Forbid
6060
- Replace
6161
type: string
62+
defaultedObject:
63+
default:
64+
- nested:
65+
bar: true
66+
foo: baz
67+
- nested:
68+
bar: false
69+
description: This tests that object defaulting can be performed.
70+
items:
71+
properties:
72+
nested:
73+
properties:
74+
bar:
75+
type: boolean
76+
foo:
77+
type: string
78+
required:
79+
- bar
80+
- foo
81+
type: object
82+
required:
83+
- nested
84+
type: object
85+
type: array
86+
defaultedSlice:
87+
default:
88+
- a
89+
- b
90+
description: This tests that slice defaulting can be performed.
91+
items:
92+
type: string
93+
type: array
94+
defaultedString:
95+
default: forty-two
96+
description: This tests that primitive defaulting can be performed.
97+
type: string
6298
failedJobsHistoryLimit:
6399
description: The number of failed finished jobs to retain. This is a
64100
pointer to distinguish between explicit zero and not specified.
@@ -4790,6 +4826,9 @@ spec:
47904826
required:
47914827
- binaryName
47924828
- canBeNull
4829+
- defaultedObject
4830+
- defaultedSlice
4831+
- defaultedString
47934832
- jobTemplate
47944833
- schedule
47954834
- twoOfAKindPart0

pkg/markers/parse.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,19 @@ func MakeDefinition(name string, target TargetType, output interface{}) (*Defini
865865
return def, nil
866866
}
867867

868+
// MakeAnyTypeDefinition constructs a definition for an output struct with a
869+
// field named `Value` of type `interface{}`. The argument to the marker will
870+
// be parsed as AnyType and assigned to the field named `Value`.
871+
func MakeAnyTypeDefinition(name string, target TargetType, output interface{}) (*Definition, error) {
872+
defn, err := MakeDefinition(name, target, output)
873+
if err != nil {
874+
return nil, err
875+
}
876+
defn.FieldNames = map[string]string{"": "Value"}
877+
defn.Fields = map[string]Argument{"": defn.Fields["value"]}
878+
return defn, nil
879+
}
880+
868881
// splitMarker takes a marker in the form of `+a:b:c=arg,d=arg` and splits it
869882
// into the name (`a:b`), the name if it's not a struct (`a:b:c`), and the parts
870883
// that are definitely fields (`arg,d=arg`).

pkg/markers/parse_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,8 @@ var _ = Describe("Parsing", func() {
7676
mustDefine(reg, "testing:tripleDefined", DescribesField, "")
7777
mustDefine(reg, "testing:tripleDefined", DescribesType, false)
7878

79-
defn, err := MakeDefinition("testing:custom", DescribesPackage, CustomType{})
79+
defn, err := MakeAnyTypeDefinition("testing:custom", DescribesPackage, CustomType{})
8080
Expect(err).NotTo(HaveOccurred())
81-
defn.FieldNames = map[string]string{"": "Value"}
82-
defn.Fields = map[string]Argument{"": defn.Fields["value"]}
8381

8482
Expect(reg.Register(defn)).To(Succeed())
8583
})

test.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ setup_envs
109109

110110
header_text "running golangci-lint"
111111

112+
header_text "generating marker help"
113+
pushd cmd/controller-gen > /dev/null
114+
go generate
115+
popd > /dev/null
116+
112117
golangci-lint run --disable-all \
113118
--enable=misspell \
114119
--enable=golint \

0 commit comments

Comments
 (0)