Skip to content

Commit 6a77301

Browse files
committed
Add a marker to support field defaulting in crd schema
1 parent 837af48 commit 6a77301

File tree

5 files changed

+66
-3
lines changed

5 files changed

+66
-3
lines changed

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/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/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)