Skip to content

Conversation

@apelisse
Copy link
Contributor

This does mostly two things:

  • Defines the schema for OneOf: multiple oneof can be at the same level (in the same structure),
  • Normalizes an object based on oneofs definition: uses the discriminator if present and has changed, uses the difference of fields if they changed, updates the discriminator if present.

And adds tests. This is still not plugged anyway though, so it's not going to do anything.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Mar 20, 2019
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: apelisse

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

The pull request process is described here

Details 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 approved Indicates a PR has been approved by an approver from all required OWNERS files. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 20, 2019
@apelisse
Copy link
Contributor Author

/assign @lavalamp @kwiesmueller @jennybuckley

typed/union.go Outdated
@@ -0,0 +1,130 @@
/*
Copyright 2018 The Kubernetes Authors.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2019

@@ -0,0 +1,224 @@
/*
Copyright 2018 The Kubernetes Authors.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2019

typed/union.go Outdated
return set
}

// Set the discriminator to a specific value

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get

typed/union.go Outdated
if union.Discriminator != nil {
df = uncapitalize(*union.Discriminator)
odv := getDiscriminatorValue(old, df)
ndv := getDiscriminatorValue(new, df)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need to uncapitalize the odv and ndv? It seems a bit kubernetes specific.

unions:
- discriminator: discriminator
fields: [a, b, c]
- fields: [one, two, three]`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test looks good, maybe we should also try some nested structs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we should test that creating a new object should still set the discriminator correctly

typed/union.go Outdated

for field := range ofs {
if _, ok := nfs[field]; !ok {
out.MapValue.Delete(field)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could use the same

            for _, field := range union.Fields {
				if field != ndv {
					out.MapValue.Delete(field)
				}
			}

loop from before (or a function that does that) and not worry about ofs or nfs anymore?

typed/union.go Outdated
}

if df != "" {
out.MapValue.Set(df, value.StringValue(dv))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value.StringValue(capitalize(dv))?

// one or more fields in the above list. A given field from the above
// list may be referenced in exactly 0 or 1 places in the below list.
// Unions []Union `yaml:"unions,omitempty"`
Unions []Union `yaml:"unions,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the reasons this was commented out is that I wasn't sure if I wanted to do it the way you've added it, or if it would be better to use a struct per union and support embedding a list of structs. The other way would make it impossible to try and put the same field into multiple unions. I'm not sure where, but it'd be nice to see a discussion of why this format is chosen. (Maybe you have it and I haven't seen it yet.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way it's going, we're only going to accept one union per structure in the OpenAPI, which would prevent any sort of overlap between fields

// one or more fields in the above list. A given field from the above
// list may be referenced in exactly 0 or 1 places in the below list.
// Unions []Union `yaml:"unions,omitempty"`
Unions []Union `yaml:"unions,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also consider a different name, since you have to explain "Union". Maybe Exclusivities or OneOfs?

Copy link
Contributor

@lavalamp lavalamp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super exciting change, I especially appreciate the thorough tests!

}

// Union, or oneof, means that only one of multiple fields of a structure can be
// set at all times. For backward compatibility reasons, and to help "dumb
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// set at all times. For backward compatibility reasons, and to help "dumb
// set at a time. For backward compatibility reasons, and to help "dumb

// set at all times. For backward compatibility reasons, and to help "dumb
// clients" that are not aware of union (or can't be aware of this because they
// don't know what fields are part of the union), the code tolerates multiple
// fields to be set but will try to detect which fields must be cleaned (there
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// fields to be set but will try to detect which fields must be cleaned (there
// fields to be set but will try to detect which fields must be cleared (there

// don't know what fields are part of the union), the code tolerates multiple
// fields to be set but will try to detect which fields must be cleaned (there
// should never be more than two though):
// - If the Discriminator field exist, the value of the field will be used to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// - If the Discriminator field exist, the value of the field will be used to
// - If the Discriminator field exists, the value of the field will be used to


// Union, or oneof, means that only one of multiple fields of a structure can be
// set at all times. For backward compatibility reasons, and to help "dumb
// clients" that are not aware of union (or can't be aware of this because they
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// clients" that are not aware of union (or can't be aware of this because they
// clients" which are not aware of the union (or can't be aware of it, because they

// fields to be set but will try to detect which fields must be cleaned (there
// should never be more than two though):
// - If the Discriminator field exist, the value of the field will be used to
// decide which field to clean and which field to keep
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// decide which field to clean and which field to keep
// decide which field to clear and which field to keep

{
name: "client sends multiple with discriminator",
old: `{}`,
new: `{"a": 1, "b": 1, "discriminator": "a"}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think this should cause an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the use-case in mind here is the following:

  • I'm using kustomize
  • my base deployment uses an emptyDir
  • my overlay uses a persistent disk
  • kustomize typically merges this by having both (you have to explicitely set emptyDir to null)

In that case, you could use the discriminator to clarify (which is much better than setting discriminator to null)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like kustomize should use this library to do the merges. It'd be pretty nifty if customize produced output which had each overlay as a separate field manager.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this would mean someone could send

deploymentStrategyType: Recreate
rollingUpdate:
  maxUnavailable: 1

and it would be accepted and treated as this now?

deploymentStrategyType: Recreate
rollingUpdate: {}

name: "change discriminator, nothing else",
old: `{"discriminator": "a"}`,
new: `{"discriminator": "random"}`,
out: `{"discriminator": "random"}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random discriminator values should cause an error, don't you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is similar to the one below, which is to make sure changing a DeploymentStrategyType from RollingUpdate to Recreate clears the RollingUpdate field.
Recreate isn't a name of a field in the Union but it is an allowed value of DeploymentStrategyType. If it's not allowed as a DeploymentStrategyType, I think validation outside smd could handle that kind of thing.

name: "remove discriminator, nothing else",
old: `{"discriminator": "a", "a": 1}`,
new: `{"a": 1}`,
out: `{"a": 1}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I would have expected a to be cleared also--not 100% sure.

{
name: "new object has three of same union set",
old: `{"a": 1}`,
new: `{"a": 2, "b": 1, "c": 3}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should cause an error, I think.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would cause an error outside smd as well

{
name: "old object has two of same union set",
old: `{"a": 1, "b": 2}`,
new: `{"a": 2, "b": 1}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like a test case exactly like this except only one of the values changes.

@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 22, 2019
// parent structure. Discriminator (if oneOf has one), is NOT included
// in this list. The value for field is how we map the name of the field
// to actual value for discriminator.
Fields map[string]string `yaml:"fields,omitempty"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can a value be mapped to from multiple keys?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe this should be a list, since we use lists in all the other schema pieces

Copy link
Contributor Author

@apelisse apelisse Apr 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe this should be a list, since we use lists in all the other schema pieces

Good point

can a value be mapped to from multiple keys?

No, it shouldn't. The part that converts the openapi into this schema is going to verify that this is not possible. If it still happens that we have two mappings going to the same key, it will probably use the last one in the list.

@apelisse apelisse force-pushed the oneof branch 2 times, most recently from 4f934de to ceb39c1 Compare April 8, 2019 17:33
FieldName string `yaml:"fieldName"`
// DiscriminatedBy is the value of the discriminator to select that
// field.
DiscriminatedBy string `yaml:"DiscriminatedBy"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would this field be set to in the case of Unions that don't have a discriminator?

Discriminator *string `yaml:"discriminator,omitempty"`

// This is the list of fields that belong to this union. This fields are
// required to not be part of another union, or be the discriminator for

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one union now

@apelisse
Copy link
Contributor Author

apelisse commented Apr 8, 2019

Fixed

Antoine Pelisse added 3 commits April 10, 2019 12:18
Normalization can return errors if something very wrong is happening,
like setting two new fields, or if setting a new field while also
changing the discriminator.
@jennybuckley
Copy link

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Apr 16, 2019
@k8s-ci-robot k8s-ci-robot merged commit b2ed7e1 into kubernetes-sigs:master Apr 16, 2019
@apelisse
Copy link
Contributor Author

I forgot to cc @sttts, I'll happily update code based on your comments Stefan!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants