diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index 234d9aa02fa..7bb678d56f7 100644 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -731,7 +731,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 diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 6a78067ff41..8889fb1b6f9 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -520,6 +520,7 @@ output-base output-directory output-file-base output-package +output-patch output-print-type output-version out-version diff --git a/pkg/kubectl/cmd/edit.go b/pkg/kubectl/cmd/edit.go index fecb026fa30..e4728b60746 100644 --- a/pkg/kubectl/cmd/edit.go +++ b/pkg/kubectl/cmd/edit.go @@ -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", gruntime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)") cmdutil.AddApplyAnnotationVarFlags(cmd, &options.ApplyAnnotation) diff --git a/pkg/kubectl/cmd/edit_test.go b/pkg/kubectl/cmd/edit_test.go index dd5c7b217b0..9ed51efa41b 100644 --- a/pkg/kubectl/cmd/edit_test.go +++ b/pkg/kubectl/cmd/edit_test.go @@ -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"` @@ -248,6 +249,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) } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/0.request b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/0.request new file mode 100755 index 00000000000..e69de29bb2d diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/0.response b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/0.response new file mode 100755 index 00000000000..9d4d9c7750a --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/0.response @@ -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": {} + } +} + diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/1.edited b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/1.edited new file mode 100755 index 00000000000..cce0483ddab --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/1.edited @@ -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: {} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/1.original b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/1.original new file mode 100755 index 00000000000..1748e03ca75 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/1.original @@ -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: {} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/2.request b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/2.request new file mode 100755 index 00000000000..b26622634f6 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/2.request @@ -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" + } + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/2.response b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/2.response new file mode 100755 index 00000000000..cf658cb8d1c --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/2.response @@ -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": {} + } +} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml new file mode 100644 index 00000000000..0b0f92f9754 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-edit-output-patch/test.yaml @@ -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 diff --git a/pkg/kubectl/cmd/util/editor/editoptions.go b/pkg/kubectl/cmd/util/editor/editoptions.go index 3a5e649bcde..9d31388aa6e 100644 --- a/pkg/kubectl/cmd/util/editor/editoptions.go +++ b/pkg/kubectl/cmd/util/editor/editoptions.go @@ -52,6 +52,7 @@ type EditOptions struct { resource.FilenameOptions Output string + OutputPatch bool WindowsLineEndings bool cmdutil.ValidateOptions @@ -95,6 +96,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 @@ -452,6 +457,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))