Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRD validation doesn't accept empty values for type "object" fields #25

Closed
pwittrock opened this issue Feb 16, 2018 · 18 comments
Closed
Assignees
Labels
lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale.

Comments

@pwittrock
Copy link
Member

Create example CRD, but make spec an object

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  version: v1
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct
  validation:
   # openAPIV3Schema is the schema for validating custom objects.
    openAPIV3Schema:
      properties:
        spec:
          type: object
          properties:
            cronSpec:
              type: string
              pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
            replicas:
              type: integer
              minimum: 1
              maximum: 10

Create an instance with an empty spec

apiVersion: stable.example.com/v1
kind: CronTab
metadata:
  name: c
spec:

This throws the following error, but shouldn't since spec doesn't have any required fields:

The CronTab "c" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"stable.example.com/v1", "kind":"CronTab", "metadata":map[string]interface {}{"name":"c", "namespace":"default", "creationTimestamp":"2018-02-16T15:53:27Z", "uid":"86df4c72-1331-11e8-8944-42010a800029", "selfLink":"", "clusterName":""}, "spec":interface {}(nil)}: validation failure list:

@pwittrock pwittrock changed the title CRD validation doesn't except empty values for type "object" fields CRD validation doesn't accept empty values for type "object" fields Feb 16, 2018
@nikhita
Copy link
Member

nikhita commented Feb 22, 2018

@pwittrock If spec is defined as an object in the schema, for it to be empty, it can have an empty object value but not a null value i.e. the following would work:

apiVersion: stable.example.com/v1
kind: CronTab
metadata:
  name: c
spec: {}

/cc @sttts

@sttts
Copy link
Contributor

sttts commented Feb 27, 2018

I agree with @pwittrock that the error is unexpected from a user point of view. Actually I am surprised that the given yaml translates to the null in JSON. Is this like that by definition of yaml?

@enisoc
Copy link
Member

enisoc commented Feb 27, 2018

Actually I am surprised that the given yaml translates to the null in JSON. Is this like that by definition of yaml?

Yes, I think it is:

YAML allows the node content to be omitted in many cases. Nodes with empty content are interpreted as if they were plain scalars with an empty value. Such nodes are commonly resolved to a “null” value.

http://yaml.org/spec/1.2/spec.html#id2786563

This seems more like a YAML gotcha than a CRD validation gotcha. Although spec has no required fields, the provided schema says that spec itself is required, so null is invalid. If spec were completely optional, the schema should say it must be either null or an object.

@sttts
Copy link
Contributor

sttts commented Feb 27, 2018

Yes, the OpenAPI spec does not accept null for type: object. So the validator is correct.

One can add null to the spec though:

openAPIV3Schema:
      properties:
        spec:
          anyOf:
          - type: "null"
          - type: object
            properties:
            ...

@nikhita
Copy link
Member

nikhita commented Mar 12, 2018

This is not ideal but as @enisoc mentioned

This seems more like a YAML gotcha than a CRD validation gotcha.

Closing. Please reopen if you think this is still an issue.
/close

@mbohlool
Copy link
Contributor

mbohlool commented Oct 2, 2018

the provided schema says that spec itself is required,

where it said spec is required?

the OpenAPI spec does not accept null for type: object.

I don't think spec is said something about this. It look like this is a validation bug. If the field is not in Required list, it is optional and both null or empty object should be accepted.

Analogous to that is string, if a string field is optional, null or empty string both should be accepted as not provided.

@enisoc
Copy link
Member

enisoc commented Oct 2, 2018

where it said spec is required?

You're right; I was conflating "required" and "can't be null". The schema doesn't say spec is required, but it does say that if spec is present in the object, it can't be null.

If the field is not in Required list, it is optional and both null or empty object should be accepted.

That's how "optional" works in k8s APIs, but unfortunately OpenAPI Schema (via JSON Schema) works differently.

In JSON Schema, required is defined as:

An object instance is valid against this keyword if its property set contains all elements in this keyword's array value.

It is only concerned with whether or not the property set of the object being validated contains the given fields. If you pass spec: null as in this case, the field is present, so whether it's required or not doesn't enter into it.

if a string field is optional, null or empty string both should be accepted

I agree that makes sense intuitively, but it's not how JSON Schema is defined. Notice in the data model, a null value is an instance with type: null, and no other type lists null among the range of possible values:

JSON Schema interperts documents according to a data model.  A JSON
   value interperted according to this data model is called an
   "instance".

   An instance has one of six primitive types, and a range of possible
   values depending on the type:

   null  A JSON "null" production

   boolean  A "true" or "false" value, from the JSON "true" or "false"
      productions

   object  An unordered set of properties mapping a string to an
      instance, from the JSON "object" production

   array  An ordered list of instances, from the JSON "array" production

   number  An arbitrary-precision, base-10 decimal number value, from
      the JSON "number" production

   string  A string of Unicode code points, from the JSON "string"
      production

That means, according to JSON Schema, null should fail to validate against type: string as well.

@mbohlool
Copy link
Contributor

mbohlool commented Oct 2, 2018

type: null is not supported in openapi schema, maybe that's why there is a new flag nullable in openapi v3.

in any sense, I think we should support this by patching the validation library or having a layer on top of validation to set all optional null object field to empty object before sending it to validation. This is the way our optional works and we should do the same here.

@mbohlool mbohlool reopened this Oct 2, 2018
@enisoc
Copy link
Member

enisoc commented Oct 2, 2018

I agree that matching the meaning of "optional" used by core APIs would be a nice UX, but it would mean we no longer match the OpenAPIV3 and JSON Schema specs, and that's a break that can't be taken lightly.

@lavalamp recently proposed a policy within SIG API Machinery that, if we say we use a standard (e.g. JSON Patch), any failure to match it exactly is a bug. So at best, we would need to stop saying we use OpenAPIV3, which means changing the field name or adding a new field for kubernetesAPISchema which is "mostly like OpenAPIV3 but see this doc for a list of differences".

bobcatfish added a commit to bobcatfish/pipeline that referenced this issue Oct 5, 2018
When we were using kubebuilder, it generated openAPIV3schema validation
for us in our CRD declarations. However:

1. No other knative projects use this
2. Continuing to use this is forcing us to diverge from the other
   Knative projects, specifically I found that `Status` fields, although
   optional, were being required by the validation (as described at
   kubernetes/apiextensions-apiserver#25), and
   the best way to work around it seemed to be to make them nullable (as
   per
   https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go)
   i.e. pointers, which is different from how Knative works, and
   introduces new problems (such as handling when these fields are null)
3. We (or at least @pivotal-nader-ziada and @shashwathi for sure!) were
   maintaining these fiels manually :O
bobcatfish added a commit to bobcatfish/pipeline that referenced this issue Oct 5, 2018
When we were using kubebuilder, it generated openAPIV3schema validation
for us in our CRD declarations. However:

1. No other knative projects use this
2. Continuing to use this is forcing us to diverge from the other
   Knative projects, specifically I found that `Status` fields, although
   optional, were being required by the validation (as described at
   kubernetes/apiextensions-apiserver#25), and
   the best way to work around it seemed to be to make them nullable (as
   per
   https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go)
   i.e. pointers, which is different from how Knative works, and
   introduces new problems (such as handling when these fields are null)
3. We (or at least @pivotal-nader-ziada and @shashwathi for sure!) were
   maintaining these fiels manually :O
knative-prow-robot pushed a commit to tektoncd/pipeline that referenced this issue Oct 5, 2018
When we were using kubebuilder, it generated openAPIV3schema validation
for us in our CRD declarations. However:

1. No other knative projects use this
2. Continuing to use this is forcing us to diverge from the other
   Knative projects, specifically I found that `Status` fields, although
   optional, were being required by the validation (as described at
   kubernetes/apiextensions-apiserver#25), and
   the best way to work around it seemed to be to make them nullable (as
   per
   https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go)
   i.e. pointers, which is different from how Knative works, and
   introduces new problems (such as handling when these fields are null)
3. We (or at least @pivotal-nader-ziada and @shashwathi for sure!) were
   maintaining these fiels manually :O
@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Dec 31, 2018
@fejta-bot
Copy link

Stale issues rot after 30d of inactivity.
Mark the issue as fresh with /remove-lifecycle rotten.
Rotten issues close after an additional 30d of inactivity.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle rotten

@k8s-ci-robot k8s-ci-robot added lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Jan 31, 2019
@aermakov-zalando
Copy link

/remove-lifecycle rotten

@k8s-ci-robot k8s-ci-robot removed the lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. label Feb 8, 2019
@aermakov-zalando
Copy link

Are there any updates on this? We have a CRD that (for historical reasons) has one of the elements defined as either null or a list of strings. Right now I don't see any way of writing a schema for it, because the obvious solution of "oneOf: /<type: null>" isn't valid.

@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label May 9, 2019
@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@sttts
Copy link
Contributor

sttts commented May 9, 2019

This is fixed with nullable support in 1.14. The example

...
spec:

is equivalent to spec: null and nullable: true can allow that in the schema.

@objcoding
Copy link

Yes, the OpenAPI spec does not accept null for type: object. So the validator is correct.

One can add null to the spec though:

openAPIV3Schema:
      properties:
        spec:
          anyOf:
          - type: "null"
          - type: object
            properties:
            ...

spec.validation.openAPIV3Schema.properties[spec].properties[sentinel].anyOf[0].type: Unsupported value: "null": supported values: "array", "boolean", "integer", "number", "object", "string"

@sttts
Copy link
Contributor

sttts commented Jan 13, 2021

nullable: true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale.
Projects
None yet
Development

No branches or pull requests

9 participants