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

Add --override-type flag to kubectl run and kubectl expose #105140

Merged
merged 1 commit into from Nov 5, 2021

Conversation

brianpursley
Copy link
Member

What type of PR is this?

/kind feature

What this PR does / why we need it:

Currently if you use the --overrides flag when running kubectl run or kubectl 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:

Override Type Description
merge Use an RFC7396 JSON Merge Patch. This is the current behavior and remains the default if you don't specify an override type
json Use an RFC6902 JSON Patch
strategic Use a Strategic Merge Patch

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 PR

$ kubectl run curl -n a0008 --image=mcr.microsoft.com/azure-cli --dry-run=client -o yaml --overrides='{"spec":{"containers":[{"name":"curl","resources":{"limits":{"cpu":"200m"}}}]}}' --override-type merge
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: curl
  name: curl
  namespace: a0008
spec:
  containers:
  - name: curl
    resources:
      limits:
        cpu: 200m
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
Example 2: --override-type json (RFC6902 JSON Patch)
$ kubectl run curl -n a0008 --image=mcr.microsoft.com/azure-cli --dry-run=client -o yaml --overrides='[{"op":"add","path":"/spec/containers/0/resources","value":{"limits":{"cpu":"200m"}}}]' --override-type json
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: curl
  name: curl
  namespace: a0008
spec:
  containers:
  - image: mcr.microsoft.com/azure-cli
    name: curl
    resources:
      limits:
        cpu: 200m
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
Example 3: --override-type strategic (Strategic Merge Patch)
$ kubectl run curl -n a0008 --image=mcr.microsoft.com/azure-cli --dry-run=client -o yaml --overrides='{"spec":{"containers":[{"name":"curl","resources":{"limits":{"cpu":"200m"}}}]}}' --override-type strategic
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: curl
  name: curl
  namespace: a0008
spec:
  containers:
  - image: mcr.microsoft.com/azure-cli
    name: curl
    resources:
      limits:
        cpu: 200m
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Which issue(s) this PR fixes:

Fixes kubernetes/kubectl#1101

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Added the ability to specify whether to use an RFC7396 JSON Merge Patch, an RFC6902 JSON Patch, or a Strategic Merge Patch to perform an override of the resources created by kubectl run and kubectl expose.

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:


@k8s-ci-robot k8s-ci-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. kind/feature Categorizes issue or PR as related to a new feature. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels Sep 20, 2021
@brianpursley
Copy link
Member Author

/sig cli

@k8s-ci-robot k8s-ci-robot added sig/cli Categorizes an issue or PR as relevant to SIG CLI. and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Sep 20, 2021
@brianpursley
Copy link
Member Author

/cc @eddiezane @soltysh

@brianpursley
Copy link
Member Author

/retest

@eddiezane
Copy link
Member

eddiezane commented Sep 22, 2021

@ckittel could you please give this a spin if you get a chance?

You can checkout the branch and build kubectl with make kubectl. Built binary will be at _output/bin/kubectl

@ckittel
Copy link

ckittel commented Sep 22, 2021

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 --override-type strategic and without any override-type defined (default behavior).

Also confirmed that --limits emits the "no effect" message and actually has no effect :)

Thank you!

@pacoxu pacoxu added this to Needs review in SIG CLI Sep 27, 2021
Copy link
Member

@eddiezane eddiezane left a comment

Choose a reason for hiding this comment

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

Looks good!

staging/src/k8s.io/kubectl/pkg/cmd/run/run.go Outdated Show resolved Hide resolved
staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go Outdated Show resolved Hide resolved
staging/src/k8s.io/kubectl/pkg/cmd/expose/expose_test.go Outdated Show resolved Hide resolved
@brianpursley
Copy link
Member Author

/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" {
Copy link
Contributor

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?

Copy link
Member Author

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:

  1. Inside Run() which creates the pod
  2. 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.

Copy link
Contributor

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?

Copy link
Member Author

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?

@lauchokyip
Copy link
Member

lauchokyip commented Oct 12, 2021

Hi @brianpursley
There is a big change over here for this good-first-issue PR #99891 so I wondered if you want to help reviewing and get that PR merged and modify your changes according or the other around?

I would prefer the former tho. Thanks!

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 22, 2021
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 23, 2021
@brianpursley
Copy link
Member Author

rebased

@brianpursley
Copy link
Member Author

/retest

@brianpursley
Copy link
Member Author

ping @KnVerey @eddiezane

@KnVerey Your most recent comment, you had some concerns about createGeneratedObject not being generic and having to handle a case specific to Pods, when it can be called for Pods or Services.

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?

@eddiezane you reviewed this PR previously, any additional comments after the change?

Copy link
Contributor

@KnVerey KnVerey left a 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) {
Copy link
Contributor

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).

Copy link
Member Author

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.

@brianpursley brianpursley force-pushed the kubectl-1101 branch 3 times, most recently from d79892d to 1f72277 Compare October 26, 2021 01:19
@brianpursley
Copy link
Member Author

/retest

Copy link
Contributor

@soltysh soltysh left a 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

@k8s-ci-robot k8s-ci-robot added triage/accepted Indicates an issue or PR is ready to be actively worked on. priority/backlog Higher priority than priority/awaiting-more-evidence. and removed needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels Nov 3, 2021
@brianpursley brianpursley force-pushed the kubectl-1101 branch 2 times, most recently from 8ecab68 to 27e335f Compare November 5, 2021 01:20
@brianpursley
Copy link
Member Author

/retest

1 similar comment
@brianpursley
Copy link
Member Author

/retest

…he choice of using a JSON Patch or Strategic Merge Patch to apply the override to the generated output.
Copy link
Contributor

@soltysh soltysh left a comment

Choose a reason for hiding this comment

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

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 5, 2021
@k8s-ci-robot
Copy link
Contributor

[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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Nov 5, 2021
@soltysh
Copy link
Contributor

soltysh commented Nov 5, 2021

/refresh

@k8s-ci-robot k8s-ci-robot merged commit 47041cd into kubernetes:master Nov 5, 2021
SIG CLI automation moved this from Needs review to Done Nov 5, 2021
@k8s-ci-robot k8s-ci-robot added this to the v1.23 milestone Nov 5, 2021
@brianpursley brianpursley deleted the kubectl-1101 branch February 2, 2023 01:22
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. area/kubectl cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/backlog Higher priority than priority/awaiting-more-evidence. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/cli Categorizes an issue or PR as relevant to SIG CLI. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
Development

Successfully merging this pull request may close these issues.

Converting from usage of kubectl run --limits to --overrides not clear
7 participants