Merge pull request #46091 from xilabao/new-output-in-edit

Automatic merge from submit-queue (batch tested with PRs 46091, 48280)

allow output patch string in edit command

**What this PR does / why we need it**:
allow user to get the patch from edit command if user is not familiar with the patch format.

```
# ./cluster/kubectl.sh create role a --verb=get,list --resource=no
role "a" created

# ./cluster/kubectl.sh edit role a --output-patch=true
Patch: {"rules":[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","delete"]}]}
role "a" edited

# ./cluster/kubectl.sh create role b --verb=get,list --resource=no
role "b" created

# ./cluster/kubectl.sh patch role b -p '{"rules":[{"apiGroups":[""],"resources":["nodes"],"verbs":["get","list","delete"]}]}'
role "b" patched
```
**Which issue this PR fixes**: fixes #47173

**Special notes for your reviewer**:

**Release note**:

```release-note
Could get the patch from kubectl edit command
```
pull/6/head
Kubernetes Submit Queue 2017-07-16 18:04:42 -07:00 committed by GitHub
commit 8ce6378512
12 changed files with 197 additions and 1 deletions

View File

@ -797,7 +797,7 @@ __EOF__
chmod +x /tmp/tmp-editor.sh
# Pre-condition: valid-pod POD has image nginx
kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:'
EDITOR=/tmp/tmp-editor.sh kubectl edit "${kube_flags[@]}" pods/valid-pod
[[ "$(EDITOR=/tmp/tmp-editor.sh kubectl edit "${kube_flags[@]}" pods/valid-pod --output-patch=true | grep Patch:)" ]]
# Post-condition: valid-pod POD has image gcr.io/google_containers/serve_hostname
kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'gcr.io/google_containers/serve_hostname:'
# cleaning

View File

@ -526,6 +526,7 @@ output-base
output-directory
output-file-base
output-package
output-patch
output-print-type
output-version
out-version

View File

@ -107,6 +107,7 @@ func NewCmdEdit(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmdutil.AddValidateOptionFlags(cmd, &options.ValidateOptions)
cmd.Flags().StringVarP(&options.Output, "output", "o", "yaml", "Output format. One of: yaml|json.")
cmd.Flags().BoolVarP(&options.OutputPatch, "output-patch", "", false, "Output the patch if the resource is edited.")
cmd.Flags().BoolVar(&options.WindowsLineEndings, "windows-line-endings", runtime.GOOS == "windows",
"Defaults to the line ending native to your platform.")

View File

@ -49,6 +49,7 @@ type EditTestCase struct {
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
Output string `yaml:"outputFormat"`
OutputPatch string `yaml:"outputPatch"`
SaveConfig string `yaml:"saveConfig"`
Namespace string `yaml:"namespace"`
ExpectedStdout []string `yaml:"expectedStdout"`
@ -250,6 +251,9 @@ func TestEdit(t *testing.T) {
if len(testcase.Output) > 0 {
cmd.Flags().Set("output", testcase.Output)
}
if len(testcase.OutputPatch) > 0 {
cmd.Flags().Set("output-patch", testcase.OutputPatch)
}
if len(testcase.SaveConfig) > 0 {
cmd.Flags().Set("save-config", testcase.SaveConfig)
}

View File

@ -0,0 +1,38 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
},
"creationTimestamp": "2017-02-27T19:40:53Z",
"labels": {
"app": "svc1"
},
"name": "svc1",
"namespace": "edit-test",
"resourceVersion": "670",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "a6c11186-fd24-11e6-b53c-480fcf4a5275"
},
"spec": {
"clusterIP": "10.0.0.204",
"ports": [
{
"name": "80",
"port": 80,
"protocol": "TCP",
"targetPort": 80
}
],
"selector": {
"app": "svc1"
},
"sessionAffinity": "None",
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,32 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: 2017-02-27T19:40:53Z
labels:
app: svc1
new-label: new-value
name: svc1
namespace: edit-test
resourceVersion: "670"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: a6c11186-fd24-11e6-b53c-480fcf4a5275
spec:
clusterIP: 10.0.0.204
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,31 @@
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: 2017-02-27T19:40:53Z
labels:
app: svc1
name: svc1
namespace: edit-test
resourceVersion: "670"
selfLink: /api/v1/namespaces/edit-test/services/svc1
uid: a6c11186-fd24-11e6-b53c-480fcf4a5275
spec:
clusterIP: 10.0.0.204
ports:
- name: "80"
port: 80
protocol: TCP
targetPort: 80
selector:
app: svc1
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

View File

@ -0,0 +1,10 @@
{
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
},
"labels": {
"new-label": "new-value"
}
}
}

View File

@ -0,0 +1,38 @@
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
},
"name": "svc1",
"namespace": "edit-test",
"selfLink": "/api/v1/namespaces/edit-test/services/svc1",
"uid": "a6c11186-fd24-11e6-b53c-480fcf4a5275",
"resourceVersion":"1045",
"creationTimestamp":"2017-02-27T19:40:53Z",
"labels": {
"app": "svc1",
"new-label": "new-value"
}
},
"spec": {
"clusterIP": "10.0.0.204",
"ports": [
{
"name": "80",
"port": 80,
"protocol": "TCP",
"targetPort": 80
}
],
"selector": {
"app": "svc1"
},
"sessionAffinity": "None",
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}

View File

@ -0,0 +1,32 @@
# kubectl create namespace edit-test
# kubectl create service clusterip svc1 --tcp 80 --namespace=edit-test --save-config
# kubectl edit service svc1 --namespace=edit-test --save-config=true --output-patch=true
description: edit with flag --output-patch=true should output the patch
mode: edit
args:
- service
- svc1
saveConfig: "true"
outputPatch: "true"
namespace: edit-test
expectedStdout:
- 'Patch: {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"},"labels":{"new-label":"new-value"}}}'
- service "svc1" edited
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: edit
expectedInput: 1.original
resultingOutput: 1.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/edit-test/services/svc1
expectedContentType: application/strategic-merge-patch+json
expectedInput: 2.request
resultingStatusCode: 200
resultingOutput: 2.response

View File

@ -53,6 +53,7 @@ type EditOptions struct {
resource.FilenameOptions
Output string
OutputPatch bool
WindowsLineEndings bool
cmdutil.ValidateOptions
@ -96,6 +97,10 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []
}
o.editPrinterOptions = getPrinter(o.Output)
if o.OutputPatch && o.EditMode != NormalEditMode {
return fmt.Errorf("the edit mode doesn't support output the patch")
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
@ -577,6 +582,10 @@ func (o *EditOptions) visitToPatch(
}
}
if o.OutputPatch {
fmt.Fprintf(o.Out, "Patch: %s\n", string(patch))
}
patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch)
if err != nil {
fmt.Fprintln(o.ErrOut, results.addError(err, info))