-
Notifications
You must be signed in to change notification settings - Fork 39k
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 supports multi-version Schema, Subresources and AdditionalPrintColumns #70211
Conversation
staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go
Show resolved
Hide resolved
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
// CustomResourceColumns masks the slice so protobuf can preserve empty slice | ||
// +protobuf.nullable=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@liggitt Do we need to test the round-tripping?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as edd5736#r228339108, we can drop this and just use []CustomResourceColumnDefinition
@@ -20,6 +20,9 @@ import ( | |||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
) | |||
|
|||
// CustomResourceColumns masks the slice so protobuf can preserve empty slice | |||
type CustomResourceColumns []CustomResourceColumnDefinition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remind me why we care about distinguishing nil and [] here if we didn't care for the top-level field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concern was brought up in #67521 (comment). My understanding is that we need to make protobuf preserve [], to allow overriding with an empty list. I didn't change the top-level field because it would be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rereading that thread, it looks like the feedback there was to avoid making the distinction between nil and [] (to not do this nullable typedef). I think the len(...) == 0
checks for emptiness are sufficient. Were there specific places where we needed to be able to tell nil from [] for some reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation :) No the apiserver doesn't need to distinguish nil and [] (and defaulting happens for both cases).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great, then let's drop this and just go with []CustomResourceColumnDefinition
staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go
Show resolved
Hide resolved
} | ||
value := versions[0].AdditionalPrinterColumns | ||
for _, v := range versions { | ||
if v.AdditionalPrinterColumns == nil || !reflect.DeepEqual(v.AdditionalPrinterColumns, value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we check len(...)==0 instead of nil? should we use semantic equality rather than reflect.DeepEqual? (same question applies everywhere)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By semantic equality you mean for two column slices, we should consider them to be the same if they have same elements but in different order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean to use apiequality.Semantic.DeepEqual()
. It equates nil and empty lists, and does a few other semantic comparisons (like the absolute value of resource.Quantity fields, rather than their literal value, so 1M == 1000)
/milestone v1.13 |
006683a
to
9568794
Compare
@liggitt Addressed comments and updated integration test (I had to rebase to pick up the conversion bug fix, but the first five commits didn't change). PTAL :) |
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
// CustomResourceColumns masks the slice so protobuf can preserve empty slice | ||
// +protobuf.nullable=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as edd5736#r228339108, we can drop this and just use []CustomResourceColumnDefinition
// Per-version schemas may not all be set to identical values (top-level validation schema should be used instead) | ||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. | ||
// +optional | ||
Schema *JSONSchemaProps `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed this does not parallel the top-level field. what was the reason for specifying the type of schema (nested openAPIV3Schema
field) in the top-level field, and why does that reason not apply for the per-version schemas?
cc @sttts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that for the top-level field, we were planning on having a list of validation methods for CRs, so we defined a struct and made openAPIV3Schema
nested.
Now it becomes clear that we are not adding new methods to the CustomResourceValidation
, so we simplify the per-version schema to just JSONSchemaProps
.
also cc @mbohlool @lavalamp on the related conversation: #67840
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now it becomes clear that we are not adding new methods to the
CustomResourceValidation
, so we simplify the per-version schema to justJSONSchemaProps
.also cc @mbohlool @lavalamp on the related conversation: #67840
#67840 actually proposes supporting a schema format other than openapi v3, which is exactly the use case for nesting openapi v3 as one of potentially multiple schema types. I think we should make the per-version schema structs in v1beta1 match the top-level structs, so that we can round-trip to whatever we end up with in v1, and have a path to transition to a different schema if that's where #67840 ends up. @lavalamp @sttts do you agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. In the KEP we proposed dropping the nested struct and going with just openapi v3, then in #67840 we brought up the idea around replacing openapi v3. I'm happy to go with the nested struct if we think that will make it easier to support another schema format, before we merge this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am fine with keeping top level struct format with nested openapi v3. While last proposal on #67840 is to keep a more restricted version of openapi v3 but keeping the options open make more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More related context: #64483
I'm going to add a commit with
Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"`
first, which may look like:
spec:
versions:
- name: v1
schema:
openAPIV3Schema: ... // covers schema, defaults and static validation
anotherSchemaLanguage: ... // to be added in future?
prune: true
validator, _, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation) | ||
validationSchema, err := getCRDSchemaForVersion(&crd.Spec, v.Name) | ||
if err != nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this return the full content of the CRD spec to the custom resource caller in an error? that seems very verbose and probably targeted at the wrong user... the custom resource API caller isn't necessarily the same as the CRD author. maybe call utilruntime.HandleError
and return a generic error message about the subresources to the API caller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Yes we shouldn't return the full CRD spec to a CR client when ServeHTTP
. I think the same comment also applies to https://github.com/kubernetes/kubernetes/pull/70211/files/956879420560759244d2210acff668902ef20780#diff-2f720a7e61a4ddfcfe9a9e353be8784bR225.
May I ask what kind of generic error message do you have in mind? While the error can be triggered by spec with malformed subresources/schema/columns, the incoming HTTP request isn't necessarily asking for subresources-- it's called here in ServeHTTP
:
kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go
Line 207 in a49e4e1
crdInfo, err := r.getOrCreateServingInfoFor(crd) |
So something like the server could not properly server the CRD subresources/schema/columns
(corresponding to which field might be malformed)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, something like that would be fine
|
||
subresources, err := getCRDSubresourcesForVersion(&crd.Spec, v.Name) | ||
if err != nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment about not returning the full CRD schema to the caller in an error here
table, err := tableconvertor.New(crd.Spec.AdditionalPrinterColumns) | ||
columns, err := getCRDColumnsForVersion(&crd.Spec, v.Name) | ||
if err != nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same error comment here
continue | ||
} | ||
if crdSpec.Validation != nil && v.Schema != nil { | ||
return nil, fmt.Errorf("malformed CustomResourceDefinitionSpec: top-level and per-version schemas must be mutual exclusive; spec: %v, version: %s", *crdSpec, version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
be sure to include the CRD name in the error so whoever sees it can resolve it. we probably don't need to dump the whole spec... just the CRD name, version name, and the fact that there was a top-level and per-version value specified is enough (same comment applies to the other errors as well)
staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go
Show resolved
Hide resolved
I'm a big fan of leaving open the door for another schema format.
…On Tue, Oct 30, 2018 at 4:18 PM Haowei Cai (Roy) ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In
staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go
<#70211 (comment)>
:
> @@ -66,6 +72,24 @@ type CustomResourceDefinitionVersion struct {
// Storage flags the version as storage version. There must be exactly one
// flagged as storage version.
Storage bool `json:"storage" protobuf:"varint,3,opt,name=storage"`
+ // Schema describes the schema for CustomResource used in validation, pruning, and defaulting.
+ // Top-level and per-version schemas are mutually exclusive.
+ // Per-version schemas may not all be set to identical values (top-level validation schema should be used instead)
+ // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
+ // +optional
+ Schema *JSONSchemaProps `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"`
More related context: #64483
<#64483>
I'm going to add a commit with
Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"`
first, which may look like:
spec:
versions:
- name: v1
schema:
openAPIV3Schema: ... // covers schema, defaults and static validation
anotherSchemaLanguage: ... // to be added in future?
prune: true
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#70211 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAnglq-Vl38t8cITMyUEwnN_EDM-8FNiks5uqN4sgaJpZM4X5U_r>
.
|
e3365d0
to
2f83326
Compare
if v.Schema != nil { | ||
return v.Schema, nil | ||
} | ||
return crd.Spec.Validation, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we doing override here? we agreed not to do overrides. That means in getCRDSchemaForVersion we should call the other helper to see if there is any per-version Schema. If there is we should return v.Schema
even if it is nil
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's not doing override. I think the auto-clear strategy makes sure that both ways work the same. In the case that you described (v.Schema
is nil
while there exists other per-version schema), crd.Spec.Validation
will also be nil
, so getCRDSchemaForVersion
returns nil
in the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. Still to anybody reading this code, it look like we are overriding. It would be a cleaner code if we apply the logic here too and it wouldn't hurt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or you can check if top level field is not nil
and return it at the start of the method with a comment explaining why if it is not nil, it means all per versions will be. I still prefer the first approach though :)
if v.Subresources != nil { | ||
return v.Subresources, nil | ||
} | ||
return crd.Spec.Subresources, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
if len(v.AdditionalPrinterColumns) > 0 { | ||
return v.AdditionalPrinterColumns, nil | ||
} | ||
return crd.Spec.AdditionalPrinterColumns, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
@@ -40,6 +40,12 @@ const ( | |||
// | |||
// CustomResourceSubresources defines the subresources for CustomResources | |||
CustomResourceSubresources utilfeature.Feature = "CustomResourceSubresources" | |||
|
|||
// owner: @mbohlool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add yourself as owner here too please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/test pull-kubernetes-e2e-kops-aws |
if version != v.Name { | ||
continue | ||
} | ||
if hasPerVersionSchema(crd.Spec.Versions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can do this check earlier before for loop and return crd.Spec.Validation as early as possible then you will have less nested blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree that unnesting like this would improve readability. up to you if you want to unnest like this:
if !hasPerVersionSchema(crd.Spec.Versions) {
return crd.Spec.Validation, nil
}
if crd.Spec.Validation != nil {
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version schemas must be mutual exclusive", crd.Name, version)
}
for _, v := range crd.Spec.Versions {
if version == v.Name {
return v.Schema, nil
}
}
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
@liggitt Thanks for the suggestions. I amended latest change to the last commit. Please take a look |
@@ -149,6 +158,24 @@ type CustomResourceDefinitionVersion struct { | |||
// Storage flags the version as storage version. There must be exactly one flagged | |||
// as storage version. | |||
Storage bool | |||
// Schema describes the schema for CustomResource used in validation, pruning, and defaulting. | |||
// Top-level and per-version schemas are mutually exclusive. | |||
// Per-version schemas may not all be set to identical values (top-level validation schema should be used instead) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "may not" mean here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to "must not"
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. | ||
// +optional | ||
Schema *CustomResourceValidation | ||
// Subresources describes the subresources for CustomResources |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CustomResource
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
This was copied from the top-level Subresources comment. I updated the top-level one as well. We have similar comments for other fields (e.g. Validation describes the validation methods for CustomResources
). It might worth another PR to figure out the scope and do the clean up.
staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/test/integration/helpers.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/test/integration/helpers.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go
Show resolved
Hide resolved
/test pull-kubernetes-integration |
@sttts Comments addressed. Please take a look :) @liggitt I interpreted "squash in the comment commit" in #70211 (review) as squashing new changes in an "address comments:" commit, so that we don't have to review the functionality part from the beginning. Please let me know if you want me to squash everything together. |
lgtm once @sttts acks, can squash the review commits in and we can tag for merge |
Lgtm. Leaving #70211 (comment) to @liggitt. /approve |
schema, subresources and columns
CRD per-version schema/subresources/columns
whitespace changes)
8a3b159
to
5b545bb
Compare
Commits squashed. Please take a look |
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: liggitt, roycaihw, sttts The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What type of PR is this?
/kind feature
What this PR does / why we need it:
Part of the CRD conversion webhook design: customresource-conversion-webhook.md#top-level-fields-to-per-version-fields
Main CRD conversion webhook PR: #67006
This PR is a combination of two previous separate PRs (#67553, #67521) following the updated KEP design: kubernetes/community#2737
Special notes for your reviewer:
redundantpre-creation/update-dropping-fieldand failing-validationfor disabled feature-gated fields, please refer to the related topic in sig-api-machinery meeting (Oct 24) for more information.Two subresources integration tests (with TODO comments) will be extended to test per-version subresources after Fix custom resource handler in-memory version #70087 fixes the unstructured conversion bug.UpdatedDoes this PR introduce a user-facing change?:
/sig api-machinery
/area custom-resources
/cc @sttts @mbohlool @liggitt