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
Add --override-type flag to kubectl run and kubectl expose #105140
Conversation
/sig cli |
/cc @eddiezane @soltysh |
/retest |
@ckittel could you please give this a spin if you get a chance? You can checkout the branch and build kubectl with |
Thanks @eddiezane and @brianpursley! I built this source branch, and executed my scenario and a couple variations on the theme (including intentionally malformed patch json), and they all behaved as I would expect. I only tested Also confirmed that Thank you! |
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.
Looks good!
51b0849
to
d4b6daa
Compare
/retest |
if len(overrides) > 0 { | ||
codec := runtime.NewCodec(scheme.DefaultJSONEncoder(), scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...)) | ||
obj, err = cmdutil.Merge(codec, obj, overrides) | ||
if mapping.GroupVersionKind.Kind == "Pod" { |
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.
When would this not be a pod? If that can happen, shouldn't we return an error rather than ignoring the override data in that case?
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.
createGeneratedObject
gets called from two places:
- Inside
Run()
which creates the pod - Inside
generateService()
which creates the service when you use the--expose
arg
Those are the only two cases though.
This conditional is to control whether the override should be applied (Pod) or not (Service).
Previously, I had added a boolean parameter that was passed in to indicate whether to apply the override or not. @eddiezane suggested in a previous review comment checking Kind == Pod here instead, which I thought was a better approach because that information is available already in this function and it eliminates the need for an extra function parameter.
Let me know if this changes your thoughts above, or if you'd like to see a check in place and return an error, and I can add it to check that it is a Pod or Service and if not return an error. Also maybe a comment would help explaining why this check is performed.
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.
Hmm, I see. What still bothers me is that the fact that this function needs to know the available overrides are not always valid, and the reason for that has to do with its callers and not with something fundamental about what it is using to make the distinction (pod vs service).
I know it's more verbose, but I'd be inclined to break up this big function into the three things it does, only two of which the Service code path wants, i.e.:
obj, err := o.buildGeneratedObject(generator, names, params)
if err != nil {
return err
}
obj, err = o.ApplyOverride(obj, corev1.Pod{})
if err != nil {
return err
}
runObject, err := o.createGeneratedObject(f, cmd, obj)
if err != nil {
return err
}
WDYT of that option?
Even there, it kinda bugs me to hardcode corev1.Pod{}
at all, since the surrounding code seems to be making an effort to be generic.
Looking at the LookupPatchMeta
options available in strategicpatch
, I'm not sure how to get around that... unless we have openAPI available, maybe? Or there's some way to go from GVK (which we clearly have here) to Go struct? Or we could somehow get this override step right into the part that knows this is a pod, i.e. func (BasicPod) Generate
?
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 Katrina. I agree with your point about not wanting to make conditional paths in the generic function based on specific resource types.
I did some thinking about what you said and I came up with a slightly different approach that leaves the existing function intact and I think will work.
I introduced an Overrider
type which knows about the specific resource being overridden. An overrider can optionally be passed into createGeneratedObject()
. So in the case of Pod creation, it is passed in, but for the service created when using --expose, it is nil.
What do you think of this idea? Does it satisfy your original concern that createGeneratedObject()
should not be type checking?
d4b6daa
to
cef9fa6
Compare
Hi @brianpursley I would prefer the former tho. Thanks! |
cef9fa6
to
1719cee
Compare
rebased |
/retest |
ping @KnVerey @eddiezane @KnVerey Your most recent comment, you had some concerns about I updated the PR so this function accepts an What do you think about this approach? @eddiezane you reviewed this PR previously, any additional comments after the 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.
I updated the PR so this function accepts an Overrider parameter, allowing the override behavior to be injected into the function instead of the function needing to handle specific use cases.
What do you think about this approach?
I like it much better!
// TODO: merge assumes JSON serialization, and does not properly abstract API retrieval | ||
func Merge(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) { | ||
func Merge(dst runtime.Object, fragment string, overrideType OverrideType, dataStruct interface{}) (runtime.Object, 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.
It kinda bugs me that this function's signature needs to change to have so many parameters, and that which ones are relevant depends on overrideType
. WDYT about moving the case statement inside your new Overrider#Apply
, and having three separate, specialized util functions? That would also enable us to preserve the signature on the original, and to write doc comments that are easier to understand (the above comment should probably be expanded either way).
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.
@KnVerey I know what you're saying. The new method signature was not good because it had arguments that only sometimes were used depending on the switch. The Overrider is what knows which type of override should be performed, so it should not make Merge have to know about each override type.
I updated the code to preserve the original Merge function, and moved the switch statement into the Overrider as you suggested. Then I created new separate methods for StrategicMerge and JsonPatch. It is a little bit more code, but the method signatures are cleaner and unit tests are simpler.
Regarding this TODO that was preexisting in the code...
// TODO: merge assumes JSON serialization, and does not properly abstract API retrieval
I'm not quite sure I get what this is asking. I understand that the function relies on JSON serialization, but I'm not sure what is "to do" about that or if it is even a problem. I'm also not sure what action is needed for "proper abstract API retrieval". Personally, I'd prefer to just remove TODO comments like this and put them in the backlog as an issue, but I am hesitant to do that without understanding the intent behind it and since it's been there several years.
Thanks again for your feedback so far, it's been helpful. And let me know if you have any other ideas for improving this.
d79892d
to
1f72277
Compare
/retest |
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.
A couple of nits, ping me or @eddiezane or @KnVerey and this is good to go
/triage accepted
/priority backlog
8ecab68
to
27e335f
Compare
/retest |
1 similar comment
/retest |
…he choice of using a JSON Patch or Strategic Merge Patch to apply the override to the generated output.
27e335f
to
0e697e1
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.
/lgtm
/approve
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: brianpursley, soltysh 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 |
/refresh |
What type of PR is this?
/kind feature
What this PR does / why we need it:
Currently if you use the
--overrides
flag when runningkubectl run
orkubectl expose
, it uses an RFC7396 JSON Merge Patch to override the generated JSON.Sometimes though, it is helpful to have more flexibility and control over what gets replaced.
This PR adds a new
--override-type
flag which allows you to choose one of the following ways for the override to be performed:merge
json
strategic
For consistency, these override type names mirror the type names used by
kubectl patch
.Example 1:
--override-type merge
(RFC7396 JSON Merge Patch)This is the default and the current behavior of
--overrides
before this PRExample 2:
--override-type json
(RFC6902 JSON Patch)Example 3:
--override-type strategic
(Strategic Merge Patch)Which issue(s) this PR fixes:
Fixes kubernetes/kubectl#1101
Special notes for your reviewer:
Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: