Skip to content

Commit

Permalink
Add crd:validation:Schemaless marker
Browse files Browse the repository at this point in the history
This marker will avoid trying to do any type detection
on any struct field on which it is set. This gives
users a safety valve when they hit an edge case where
type inference does the wrong thing for them.

This fixes #291, which was recently re-broken by fixing #502

Signed-off-by: Max Smythe <smythe@google.com>
  • Loading branch information
maxsmythe committed Nov 26, 2020
1 parent 895eccc commit 80b5507
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 7 deletions.
2 changes: 0 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
run:
modules-download-mode: readonly

# Increase the default deadline from 1m as some module operations can take a
# while if uncached!
deadline: 5m
17 changes: 17 additions & 0 deletions pkg/crd/markers/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import (
"sigs.k8s.io/controller-tools/pkg/markers"
)

const (
SchemalessName = "kubebuilder:validation:Schemaless"
)

// 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.
Expand Down Expand Up @@ -82,6 +86,9 @@ var FieldOnlyMarkers = []*definitionWithHelp{

must(markers.MakeDefinition("kubebuilder:validation:EmbeddedResource", markers.DescribesField, XEmbeddedResource{})).
WithHelp(XEmbeddedResource{}.Help()),

must(markers.MakeDefinition(SchemalessName, markers.DescribesField, Schemaless{})).
WithHelp(Schemaless{}.Help()),
}

// ValidationIshMarkers are field-and-type markers that don't fall under the
Expand Down Expand Up @@ -225,6 +232,16 @@ type XPreserveUnknownFields struct{}
// field, yet it is possible. This can be combined with PreserveUnknownFields.
type XEmbeddedResource struct{}

// +controllertools:marker:generateHelp:category="CRD validation"
// Schemaless marks a field as being a schemaless object.
//
// Schemaless objects are not introspected, so you must provide
// any type and validation information yourself. One use for this
// tag is for embedding fields that hold JSONSchema typed objects.
// Because this field disables all type checking, it is recommended
// to be used only as a last resort.
type Schemaless struct{}

func (m Maximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply maximum to an integer")
Expand Down
11 changes: 11 additions & 0 deletions pkg/crd/markers/zz_generated.markerhelp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion pkg/crd/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"

apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"

"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
Expand Down Expand Up @@ -378,7 +379,12 @@ func structToSchema(ctx *schemaContext, structType *ast.StructType) *apiext.JSON
}
}

propSchema := typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), field.RawField.Type)
var propSchema *apiext.JSONSchemaProps
if field.Markers.Get(crdmarkers.SchemalessName) != nil {
propSchema = &apiext.JSONSchemaProps{}
} else {
propSchema = typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), field.RawField.Type)
}
propSchema.Description = field.Doc

applyMarkers(ctx, field.Markers, propSchema, field.RawField)
Expand Down
9 changes: 7 additions & 2 deletions pkg/crd/testdata/cronjob_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,19 @@ type CronJobSpec struct {

// This tests that min/max properties work
MinMaxProperties MinMaxObject `json:"minMaxProperties,omitempty"`

// This tests that the schemaless marker works
// +kubebuilder:validation:Schemaless
Schemaless []byte `json:"schemaless,omitempty"`
}

// +kubebuilder:validation:Type=object
// +kubebuilder:pruning:PreserveUnknownFields
type Preserved struct {
ConcreteField string `json:"concreteField"`
Rest map[string]interface{} `json:"-"`
ConcreteField string `json:"concreteField"`
Rest map[string]interface{} `json:"-"`
}

func (p *Preserved) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &p.Rest); err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5071,6 +5071,8 @@ spec:
schedule:
description: The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
type: string
schemaless:
description: This tests that the schemaless marker works
startingDeadlineSeconds:
description: Optional deadline in seconds for starting the job if
it misses scheduled time for any reason. Missed jobs executions
Expand Down
4 changes: 2 additions & 2 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ fetch_kb_tools
# setup testing env
setup_envs

header_text "running golangci-lint"

header_text "generating marker help"
pushd cmd/controller-gen > /dev/null
go generate
popd > /dev/null

header_text "running golangci-lint"

golangci-lint run --disable-all \
--enable=misspell \
--enable=golint \
Expand Down

0 comments on commit 80b5507

Please sign in to comment.