diff --git a/pkg/crd/markers/package.go b/pkg/crd/markers/package.go index ea49988a6..1d4c57432 100644 --- a/pkg/crd/markers/package.go +++ b/pkg/crd/markers/package.go @@ -24,5 +24,7 @@ func init() { AllDefinitions = append(AllDefinitions, markers.Must(markers.MakeDefinition("groupName", markers.DescribesPackage, "")), markers.Must(markers.MakeDefinition("versionName", markers.DescribesPackage, "")), + markers.Must(markers.MakeDefinition("kubebuilder:validation:Optional", markers.DescribesPackage, struct{}{})), + markers.Must(markers.MakeDefinition("kubebuilder:validation:Required", markers.DescribesPackage, struct{}{})), ) } diff --git a/pkg/crd/markers/validation.go b/pkg/crd/markers/validation.go index cf2e90ea6..fbe19bf41 100644 --- a/pkg/crd/markers/validation.go +++ b/pkg/crd/markers/validation.go @@ -26,7 +26,8 @@ import ( "sigs.k8s.io/controller-tools/pkg/markers" ) -// ValidationMarkers lists all available markers that affect CRD schema generation. +// ValidationMarkers lists all available markers that affect CRD schema generation, +// except for the few that don't make sense as type-level markers (see FieldOnlyMarkers). // All markers start with `+kubebuilder:validation:`, and continue with their type name. // A copy is produced of all markers that describes types as well, for making types // reusable and writing complex validations on slice items. @@ -60,6 +61,14 @@ var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers. Nullable(false), ) +// FieldOnlyMarkers list field-specific validation markers (i.e. those markers that don't make +// sense on a type, and thus aren't in ValidationMarkers). +var FieldOnlyMarkers = []*markers.Definition{ + markers.Must(markers.MakeDefinition("kubebuilder:validation:Required", markers.DescribesField, struct{}{})), + markers.Must(markers.MakeDefinition("kubebuilder:validation:Optional", markers.DescribesField, struct{}{})), + markers.Must(markers.MakeDefinition("optional", markers.DescribesField, struct{}{})), +} + func init() { AllDefinitions = append(AllDefinitions, ValidationMarkers...) @@ -88,6 +97,8 @@ type Enum []interface{} type Format string type Type string type Nullable bool +type Required struct{} +type Optional struct{} func (m Maximum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { if schema.Type != "integer" { diff --git a/pkg/crd/parser.go b/pkg/crd/parser.go index 10a3c3405..c2fe07126 100644 --- a/pkg/crd/parser.go +++ b/pkg/crd/parser.go @@ -151,7 +151,15 @@ func (p *Parser) NeedSchemaFor(typ TypeIdent) { p.Schemata[typ] = apiext.JSONSchemaProps{} schemaCtx := newSchemaContext(typ.Package, p) - schema := infoToSchema(schemaCtx.ForInfo(info)) + ctxForInfo := schemaCtx.ForInfo(info) + + pkgMarkers, err := markers.PackageMarkers(p.Collector, typ.Package) + if err != nil { + typ.Package.AddError(err) + } + ctxForInfo.PackageMarkers = pkgMarkers + + schema := infoToSchema(ctxForInfo) p.Schemata[typ] = *schema diff --git a/pkg/crd/schema.go b/pkg/crd/schema.go index 6e545698f..7d67727ec 100644 --- a/pkg/crd/schema.go +++ b/pkg/crd/schema.go @@ -66,6 +66,7 @@ type schemaContext struct { info *markers.TypeInfo schemaRequester schemaRequester + PackageMarkers markers.MarkerValues } // newSchemaContext constructs a new schemaContext for the given package and schema requester. @@ -342,8 +343,26 @@ func structToSchema(ctx *schemaContext, structType *ast.StructType) *v1beta1.JSO fieldName := jsonOpts[0] inline = inline || fieldName == "" // anonymous fields are inline fields in YAML/JSON - if !inline && !omitEmpty { - props.Required = append(props.Required, fieldName) + // if no default required mode is set, default to required + defaultMode := "required" + if ctx.PackageMarkers.Get("kubebuilder:validation:Optional") != nil { + defaultMode = "optional" + } + + switch defaultMode { + // if this package isn't set to optional default... + case "required": + // ...everything that's not inline, omitempty, or explicitly optional is required + if !inline && !omitEmpty && field.Markers.Get("kubebuilder:validation:Optional") == nil && field.Markers.Get("optional") == nil { + props.Required = append(props.Required, fieldName) + } + + // if this package isn't set to required default... + case "optional": + // ...everything that isn't explicitly required is optional + if field.Markers.Get("kubebuilder:validation:Required") != nil { + props.Required = append(props.Required, fieldName) + } } propSchema := typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), field.RawField.Type)