-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 conversion webhook implementation #351
✨ crd conversion webhook implementation #351
Conversation
@droot: GitHub didn't allow me to request PR reviews from the following users: shawn-hurley, JoelSpeed. Note that only kubernetes-sigs members and repo collaborators can review this PR, and authors cannot review their own PRs. In response to this: 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. |
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've taken a read over the design and over the implementation and left a few comments
I'm so glad you've started work on this @droot as I need to bump a kubebuilder API version soon and need this 😅 Let me know if there's any way I can help with this
pkg/webhook/conversion/conversion.go
Outdated
} | ||
} | ||
|
||
// neigher src nor dst are Hub, means both of them are spoke, so lets get the hub |
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.
Typo here
// neigher src nor dst are Hub, means both of them are spoke, so lets get the hub | |
// neither src nor dst are Hub, means both of them are spoke, so lets get the hub |
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.
Fixed.
pkg/webhook/conversion/conversion.go
Outdated
if err != nil { | ||
return err | ||
} | ||
// shall we get Hub for dst type as well and ensure hubs are same ? |
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.
Commented on the design doc but I think this is probably work doing, the current result, if I've read this properly, is that err = dst.(conversion.Convertible).ConvertFrom(hub)
will result in an error later down (approx failed to convert from hub version: unsupported type
) which may not be immediately obvious as to what the problem is, WDYT?
Might be worth making this a TODO?
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 added a check at the beginning of the function to ensure both src and dst belong to same API group to ensure src/dst have same hub type.
// v2.ExternalJob is the storage version so mark this as Hub | ||
// Storage version doesn't need to implement any conversion methods because | ||
// default conversionHandler implements conversion logic for storage version. | ||
// Adding comment annotation here to mark it as storage 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.
Annotation not set yet?
|
||
// since v1.ExternalJob is a spoke, it needs to be convertable. | ||
// It needs to implement convert to/from storage version | ||
// TODO(droot): evaluate advantages of taking `conversion.Hub` as input to |
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 2 cents on this is that the current design has two ways to indicate that a type is the storage version, the annotation and this conversion.Hub
, I would imagine it would be very easy to forget to do one. Is there a good way to remove one of the two?
I had kinda expected ConvertTo
and ConvertFrom
to take a runtime.Object
initially but I concluded that it's probably a disadvantage as you lose some compile time checks theoretically
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.
Is there a good way to remove one of the two?
Annotations (we have started calling them markers now because annotation/tags are so overloaded in k8s world :)) is primarily needed for controller-gen to generate the manifests for conversion webhook. Only way to remove them would be to make controller-gen parse these types and examine if they implement these methods (which can be done, but is a bit stretch looking at current state of the code in controller-gen). Till then, this is the only option.
I had kinda expected ConvertTo and ConvertFrom to take a runtime.Object initially but I concluded that it's probably a disadvantage as you lose some compile time checks theoretically
Agreed. using Hub
has type-safety as well readability from an end-user's perspective. So will took out that TODO.
@shawn-hurley I've never seen something that wasn't a little bit painful, but the process here would basically be:
While this is a bit of a pain, it's not more painful (in my experience) that the k/k method, which, from my experience with HPA v2, looks a lot like:
Now, that's not a high bar, but I also don't see a good way to make things better without supporting arbitrary conversion graphs and gradually growing a strange web of conversions. |
pkg/webhook/conversion/conversion.go
Outdated
@@ -0,0 +1,249 @@ | |||
/* | |||
Copyright 2018 The Kubernetes Authors. |
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.
It isn't 2018 :-)
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.
Fixed :)
a095ab3
to
c56ac5b
Compare
Awesome. Thanks for reviewing the PR. Usability feedback will be super important and I am glad that you will be using it your project, so keep us posted from end-user's perspective. Have addressed the comments and added a few more tests. |
Pretty much along the lines as @DirectXMan12 described above. I will add those to the design doc. |
9c4ac2f
to
c5be062
Compare
pkg/webhook/conversion/conversion.go
Outdated
var hub conversion.Hub | ||
var isHub, hubFoundAlready bool | ||
for _, gvk := range gvks { | ||
o, _ := wh.scheme.New(gvk) |
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 error should be checked here. If the gvk is not registered in the scheme, scheme.New
will return an error.
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 pt. Added catch.
} | ||
|
||
// Decode decodes the inlined object. | ||
func (d *Decoder) Decode(content []byte) (runtime.Object, *schema.GroupVersionKind, error) { |
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 feel we can move the decoder in admission pkg to a shared the place and reuse it potentially.
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.
Added a TODO for now. I am sure conversion
pkg will evolve as it goes to Beta
and we can reevaluate it at that point.
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.
not convinced we need this at all (see above comment)
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, @DirectXMan12 is right. We don't need the decoder for conversionRequest now, we only need for processing the objects embedded in the the request.
pkg/webhook/conversion/response.go
Outdated
func errored(err error) *apix.ConversionResponse { | ||
return &apix.ConversionResponse{ | ||
Result: metav1.Status{ | ||
Code: http.StatusOK, |
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.
It seems to be wrong 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.
Good catch, tool it out.
return | ||
} | ||
|
||
// TODO(droot): may be move the conversion logic to a separate module to |
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.
IMO it's a good idea and align with what admission package. It may look like
func (wh *Webhook) ServeConvert(req ConversionRequest, resp ConversionResponse) {
// Do something
}
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Decoding http request to get ConversionRequest
// Do conversion
wh.ServeConvert(convReq, convResp)
// Write the conversion response to http.ResponseWriter
}
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 want to keep this as TODO for now and revisit later. The good thing it can be done as internal refactoring later.
c5be062
to
90b9818
Compare
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 review @mengqiy. Addressed some comments. PTAL.
return | ||
} | ||
|
||
// TODO(droot): may be move the conversion logic to a separate module to |
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 want to keep this as TODO for now and revisit later. The good thing it can be done as internal refactoring later.
pkg/webhook/conversion/conversion.go
Outdated
var hub conversion.Hub | ||
var isHub, hubFoundAlready bool | ||
for _, gvk := range gvks { | ||
o, _ := wh.scheme.New(gvk) |
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 pt. Added catch.
} | ||
|
||
// Decode decodes the inlined object. | ||
func (d *Decoder) Decode(content []byte) (runtime.Object, *schema.GroupVersionKind, error) { |
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.
Added a TODO for now. I am sure conversion
pkg will evolve as it goes to Beta
and we can reevaluate it at that point.
pkg/webhook/conversion/response.go
Outdated
func errored(err error) *apix.ConversionResponse { | ||
return &apix.ConversionResponse{ | ||
Result: metav1.Status{ | ||
Code: http.StatusOK, |
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 catch, tool it out.
/cc @mbohlool |
@droot: GitHub didn't allow me to request PR reviews from the following users: mbohlool. Note that only kubernetes-sigs members and repo collaborators can review this PR, and authors cannot review their own PRs. In response to this:
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. |
785e79e
to
3b9e1e1
Compare
@mbohlool @mengqiy @JoelSpeed This is good to go from my perspective. I think this is a good starting point to get feedback for the CRD conversion feature. |
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.
Code LGTM. I'm OK having some TODOs and revisit them later.
pkg/webhook/conversion/conversion.go
Outdated
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"sigs.k8s.io/controller-runtime/pkg/conversion" | ||
|
||
"encoding/json" |
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.
nit: please group the imports.
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.
|
||
func TestConversionWebhook(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecsWithDefaultAndCustomReporters(t, "application Suite", []Reporter{envtest.NewlineReporter{}}) |
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.
application Suite
maybe conversion suite?
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.
|
||
It("should convert objects successfully", func() { | ||
|
||
v1Obj := &jobsv1.ExternalJob{ |
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.
v1Obj
is used in all test cases and is static. It can extract out of It
and put it in BeforeEach
or others.
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 know it looks more verbose in current form, but it makes the test more readable from perspective of input
and output
and expectations.
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'd just move it to outside, or at least make a helper -- it'll make it way easier to refactor later.
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.
Made a helper for this one because recently added objects using one of the core resource (Deployment).
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.
Wonder if it would make sense to add tests testing the src and dest are not convertible or if src is the hub and if dest is the hub and make sure each happy path is covered?
3b9e1e1
to
d88c13b
Compare
I just added a test to cover the case where hub is not defined. One test which is missing now is (spoke-to-spoke conversion), I will have to define a two API group under |
limitations under the License. | ||
*/ | ||
|
||
package conversion |
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.
Godocs on the package
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.
pkg/conversion/conversion.go
Outdated
ConvertFrom(src Hub) error | ||
} | ||
|
||
// Hub defines capability to indicate whether a versioned type is a Hub or not. |
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.
This sentence is a bit awkward in its construction -- I'd go with Hub marks that a given type is the hub type for conversion. This means that all conversions will first convert to the hub type, then convert from the hub type to the destination type. All types besides the hub type should implement Convertible
.
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 suggestion, that reads much better.
limitations under the License. | ||
*/ | ||
|
||
package conversion |
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.
godocs
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.
pkg/webhook/conversion/conversion.go
Outdated
) | ||
|
||
var ( | ||
log = logf.Log.WithName("conversion_webhook") |
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.
hyphens instead of underscores, generally
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.
pkg/webhook/conversion/conversion.go
Outdated
return err | ||
} | ||
|
||
// inject the decoder here too, just in case the order of calling this is not |
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.
??
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.
This is not needed, so took it out.
|
||
convReview := doRequest(convReq) | ||
|
||
Expect(convReview.Response.ConvertedObjects).Should(HaveLen(1)) |
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.
Expect().To
(more idiomatic)
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, reads better. Done
} | ||
|
||
convReview := doRequest(convReq) | ||
Expect(convReview.Response.Result.Message).Should( |
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.
blech. I hate directly checking error message strings. Can't we just check codes?
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.
Need to explore a bit more, but in general I agree checking error messages is bad.
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 explored bit more about using codes here, couldn't find an easy way to do it at this level which is conversionRequest/Response level. At this level, we can only indicate status and failure message (which contains the reason for failure). I removed reason string checking here because that makes the tests brittle.
} | ||
|
||
// Decode decodes the inlined object. | ||
func (d *Decoder) Decode(content []byte) (runtime.Object, *schema.GroupVersionKind, error) { |
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.
not convinced we need this at all (see above comment)
pkg/webhook/conversion/response.go
Outdated
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
func errored(err error) *apix.ConversionResponse { |
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.
just move this into the other file where it's used, probably
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, killed the file.
@@ -0,0 +1,25 @@ | |||
/* |
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.
move this under example/
(or just use example/
), just to be certain we don't cause import issues
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.
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 review @DirectXMan12 . Addressed the comments. PTAL.
I have addressed the comments in a separate commit for making it easier to review. I am putting it on hold so that I can squash the commits before the merge.
pkg/conversion/conversion.go
Outdated
ConvertFrom(src Hub) error | ||
} | ||
|
||
// Hub defines capability to indicate whether a versioned type is a Hub or not. |
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 suggestion, that reads much better.
pkg/webhook/conversion/conversion.go
Outdated
) | ||
|
||
var ( | ||
log = logf.Log.WithName("conversion_webhook") |
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.
pkg/webhook/conversion/conversion.go
Outdated
return err | ||
} | ||
|
||
// inject the decoder here too, just in case the order of calling this is not |
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.
This is not needed, so took it out.
pkg/webhook/conversion/conversion.go
Outdated
convertReview := &apix.ConversionReview{} | ||
// TODO(droot): figure out if we want to split decoder for conversion | ||
// request from the objects contained in the request | ||
err = wh.decoder.DecodeInto(body, convertReview) |
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.
Actually, you are right. Thanks for pointing out, this has simplified the code even further.
pkg/webhook/conversion/conversion.go
Outdated
srcGVK := src.GetObjectKind().GroupVersionKind() | ||
dstGVK := dst.GetObjectKind().GroupVersionKind() | ||
|
||
if srcGVK.GroupKind().String() != dstGVK.GroupKind().String() { |
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, you are right :)
pkg/webhook/conversion/response.go
Outdated
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
func errored(err error) *apix.ConversionResponse { |
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, killed the file.
@@ -0,0 +1,25 @@ | |||
/* |
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.
limitations under the License. | ||
*/ | ||
|
||
package conversion |
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.
limitations under the License. | ||
*/ | ||
|
||
package conversion |
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.
} | ||
|
||
convReview := doRequest(convReq) | ||
Expect(convReview.Response.Result.Message).Should( |
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.
Need to explore a bit more, but in general I agree checking error messages is bad.
4cac767
to
7cf7a99
Compare
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.
@DirectXMan12 I think this is good to go. PTAL a look.
} | ||
|
||
convReview := doRequest(convReq) | ||
Expect(convReview.Response.Result.Message).Should( |
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 explored bit more about using codes here, couldn't find an easy way to do it at this level which is conversionRequest/Response level. At this level, we can only indicate status and failure message (which contains the reason for failure). I removed reason string checking here because that makes the tests brittle.
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.
/lgtm
/approve
just tackle the nits in a follow-up -- they're not functionally blocking
|
||
// allocateDstObject returns an instance for a given GVK. | ||
func (wh *Webhook) allocateDstObject(apiVersion, kind string) (runtime.Object, error) { | ||
gvk := schema.FromAPIVersionAndKind(apiVersion, kind) |
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.
could put this in a sync.Once, or something. We can tackle in a follow-up
} | ||
|
||
scheme = kscheme.Scheme | ||
Expect(jobsapis.AddToScheme(scheme)).Should(Succeed()) |
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.
nit: Expect().To
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: DirectXMan12, droot 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 |
This PR implements CRD Conversion Webhook handler.
The design has been documented here.
Note:
Need to add more tests. I am in process of converting the design doc in markdown format (doc link shared above)