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

✨ Support for oneOf Groups for Discriminate Types #298

Closed

Conversation

mmellison
Copy link

These changes add support for the field marker: kubebuilder:validation:OneOf which will generate a oneOf block in the CRD structural definition[1]. This could be expanded in the future to include more complex oneOf objects, however for now, it works to simply define fields which are mutually exclusive:

// Discriminator Test Spec
type TestSpec struct {

	// +kubebuilder:validation:OneOf
	Cat map[string]string `json:"cat,omitempty"`

	// +kubebuilder:validation:OneOf
	Dog map[string]string `json:"dog,omitempty"`
}

becomes

        spec:
          description: Discriminator Test Spec
          oneOf:
          - properties:
              cat: {}
            required:
            - cat
          - properties:
              dog: {}
            required:
            - dog
          properties:
            cat:
              additionalProperties:
                type: string
              type: object
            dog:
              additionalProperties:
                type: string
              type: object
          type: object

This then allows the CRD to enforce a mutually exclusive type:

apiVersion: "inflow.arroyo.io/v1"
kind: Test
metadata:
  name: test-both
spec:
  cat: {"says": "meow"}
  dog: {"says": "woof"}

produces "spec" must validate one and only one schema (oneOf). Found 2 valid alternatives

apiVersion: "inflow.arroyo.io/v1"
kind: Test
metadata:
  name: test-none
spec: {}

produces "spec" must validate one and only one schema (oneOf). Found none valid


[1] https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/

@k8s-ci-robot
Copy link
Contributor

Welcome @seglberg!

It looks like this is your first PR to kubernetes-sigs/controller-tools 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes-sigs/controller-tools has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot
Copy link
Contributor

Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please follow instructions at https://git.k8s.io/community/CLA.md#the-contributor-license-agreement to sign the CLA.

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify. Thanks.


Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. label Aug 6, 2019
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: seglberg
To complete the pull request process, please assign pwittrock
You can assign the PR to them by writing /assign @pwittrock in a comment when ready.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the size/S Denotes a PR that changes 10-29 lines, ignoring generated files. label Aug 6, 2019
@DirectXMan12
Copy link
Contributor

/hold

a) you'll need to sign the CLA

b) I'd like to see a more comprehensive design for this

c) this might be a foot-gun considering some uses of one-of could produce "non-structural" schemata (https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#specifying-a-structural-schema)

d) we generally discourage this pattern, preferring unions instead (we'll need to support the x-kubernetes-union tag, though)

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 6, 2019
@mmellison
Copy link
Author

I'll gladly sign the CLA if this ends up going anywhere significant since it sounds like I may have gone down the wrong road here.

I didn't seem to come across any concepts of a Union type when digging at the current sources. I had been modeling my APIs after Kubernetes 1.15 types such as VolumeSource. Is there more information or docs you could point me towards so that I may formulate a more comprehensive design for this?

@mmellison
Copy link
Author

Ah. So I just ran across https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190425-structural-openapi.md

I'm still getting my bearing around the Kubernetes project structure... but it looks like the Union extensions have yet to land?

@DirectXMan12
Copy link
Contributor

The union thing is a new feature that codifies a pattern that we've had for a while. And yeah, it's hard to navigate those some times.

union appears to have half-landed, but I think we can produce the equivalent openapi off of the standard union markers introduced in the union kep linked in that doc (+union, +unionDeprecated, and +unionDiscriminator)

@sttts

@DirectXMan12
Copy link
Contributor

also @apelisse @jennybuckley might know more about the state of the union bits, but I don't see XUnion as part of the JSON schema types yet, but it's in kube-openapi already.

@mmellison
Copy link
Author

mmellison commented Aug 6, 2019

Producing (or "unfolding" as one of the docs put it) the equivalent openapi schema shouldn't be too difficult until there is full support for the extension. That begs the question. In the example:

x-kubernetes-unions:
- discriminator: type
  fields-discriminated:
    foo: Foo
    bar: Bar
oneOf:
  - required: ["foo"]
  - required: ["bar"]
properties:
  type:
    type: string
    enum:
    - Foo
    - Bar

should the discriminator inside the oneOf be set to a narrowed enum?

Until the apiserver supports setting the discriminator field for us, we need to lock it down so users cannot do something like setting the field foo and set the type to Bar, since this will currently pass validation.

For now, I would think a more correct expansion would be:

oneOf:
  - required: ["foo"]
    properties:
        type:
            enum:
            - Foo
  - required: ["bar"]
    properties:
        type:
            enum:
            - Bar

@DirectXMan12
Copy link
Contributor

There's an OpenAPI concept of a discriminator. That should be sufficient to prevent that case.

@apelisse
Copy link

apelisse commented Aug 9, 2019

+1 on what @DirectXMan12 says, we don't know if it's going to land in 1.16, we can't really agree on all the semantics for union, but we're working on it. Also, I don't think I'm opposed to have the "oneOf" field being generated by kube-builder. The "union" marker that we're proposing in the KEP is orthogonal and still requires validation.

@sttts
Copy link
Contributor

sttts commented Aug 12, 2019

I think unfolding does not harm. The first one looks good:

oneOf:
  - required: ["foo"]
  - required: ["bar"]
properties:
  type:
    type: string
    enum:
    - Foo
    - Bar

But the other one is wrong because {"foo": 1, "bar": 2, "type":"Foo"} passes that spec:

oneOf:
  - required: ["foo"]
    properties:
        type:
            enum:
            - Foo
  - required: ["bar"]
    properties:
        type:
            enum:
            - Bar

@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 Nov 10, 2019
@DirectXMan12
Copy link
Contributor

/lifecycle frozen

@k8s-ci-robot k8s-ci-robot added lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Nov 15, 2019
christarazi added a commit to christarazi/cilium that referenced this pull request Aug 6, 2020
To generate CRD validation schemas (also known as structual validation
schemas[1]), we bring in controller-tools. This houses a tool called
controller-gen which performs various K8s related code generation.

Note that we are using a fork of controller-tools due to the upstream
(as of v0.3.0) lacking support in two main areas.

1) A type which implements custom JSON marshalling has its validation
   schema generation skipped. In our case, api.Rule (which is set as the
   Spec field in CNP and CCNP) implements custom JSON marshalling. With
   the upstream tool, this skips the validation generation for it. In
   our fork, we revert this feature. See PR:
   kubernetes-sigs/controller-tools#427
2) For CNP and CCNP, we require `oneOf` validation for the
   endpointSelector and nodeSelector fields. This is because we want to
   validate that either endpointSelector OR nodeSelector is present, but
   not both, and at least one. In the upstream, there's currently an
   open PR that implements support for this. We have manually merged it
   in our fork. See PR:
   kubernetes-sigs/controller-tools#298

This commit also brings updates to our dependencies:

- gomega v1.7.0 -> v1.8.1
- golang.org/x/internal/{tools,event...}

[1]:
https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema

Co-authored-by: Chris Tarazi <chris@isovalent.com>
Signed-off-by: André Martins <andre@cilium.io>
Signed-off-by: Chris Tarazi <chris@isovalent.com>
aanm pushed a commit to cilium/cilium that referenced this pull request Aug 6, 2020
To generate CRD validation schemas (also known as structual validation
schemas[1]), we bring in controller-tools. This houses a tool called
controller-gen which performs various K8s related code generation.

Note that we are using a fork of controller-tools due to the upstream
(as of v0.3.0) lacking support in two main areas.

1) A type which implements custom JSON marshalling has its validation
   schema generation skipped. In our case, api.Rule (which is set as the
   Spec field in CNP and CCNP) implements custom JSON marshalling. With
   the upstream tool, this skips the validation generation for it. In
   our fork, we revert this feature. See PR:
   kubernetes-sigs/controller-tools#427
2) For CNP and CCNP, we require `oneOf` validation for the
   endpointSelector and nodeSelector fields. This is because we want to
   validate that either endpointSelector OR nodeSelector is present, but
   not both, and at least one. In the upstream, there's currently an
   open PR that implements support for this. We have manually merged it
   in our fork. See PR:
   kubernetes-sigs/controller-tools#298

This commit also brings updates to our dependencies:

- gomega v1.7.0 -> v1.8.1
- golang.org/x/internal/{tools,event...}

[1]:
https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema

Co-authored-by: Chris Tarazi <chris@isovalent.com>
Signed-off-by: André Martins <andre@cilium.io>
Signed-off-by: Chris Tarazi <chris@isovalent.com>
@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 6, 2021
@k8s-ci-robot
Copy link
Contributor

@mmellison: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels Apr 6, 2021
@displague
Copy link
Member

This approach seems like it could block adopting an approach that permits multiple oneOf required groups within a type as #461 (comment) seeks.

fieldA or fieldB are required and
fieldC or fieldD are required

@k8s-triage-robot
Copy link

The lifecycle/frozen label can not be applied to PRs.

This bot removes lifecycle/frozen from PRs because:

  • Commenting /lifecycle frozen on a PR has not worked since March 2021
  • PRs that remain open for >150 days are unlikely to be easily rebased

You can:

  • Rebase this PR and attempt to get it merged
  • Close this PR with /close

Please send feedback to sig-contributor-experience at kubernetes/community.

/remove-lifecycle frozen

@k8s-ci-robot k8s-ci-robot removed the lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. label Aug 17, 2021
@adambkaplan
Copy link

@seglberg is this PR officially obsolete?

@vincepri
Copy link
Member

Closing this due to lack of updates, feel free to pick it up again as needed

@vincepri
Copy link
Member

/close

@k8s-ci-robot
Copy link
Contributor

@vincepri: Closed this PR.

In response to this:

/close

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@JoelSpeed
Copy link
Contributor

In case anyone picks this up again, I think, from the docs about union types, that we need to support an "at most one" not an "exactly one". Based on that, I think the schema needs to be more like this below to allow 1, the other, or neither

oneOf:
- not:
    anyOf:
    - required: ["alpha"]
    - required: ["beta"]
- required: ["alpha"]
- required: ["beta"]

I also noticed that in the doc it talks about the idea of having multiple unions embedded within a single struct, to allow this we need to satisfy each union separately, so I think we can do (this represents the two inlined unions in InlinedUnion in the docs examples):

allOf:
- oneOf:
  - not:
      anyOf:
      - required: ["alpha"]
      - required: ["beta"]
  - required: ["alpha"]
  - required: ["beta"]
- oneOf:
  - not:
      anyOf:
      - required: ["fieldA"]
      - required: ["fieldB"]
  - required: ["fieldA"]
  - required: ["fieldB"]

I tested this and seemed to be able to add fieldA alone, fieldA with alpha, but then I couldn't add either fieldB or beta, which is the expected behaviour.

If no one is planning to take this work up, I might try to do this as it would be useful for something I'm working on soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/S Denotes a PR that changes 10-29 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.