From 0110f3176b7baff219f7f87087603903bd48395b Mon Sep 17 00:00:00 2001 From: feihujiang Date: Thu, 6 Aug 2015 17:24:02 +0800 Subject: [PATCH] Kubectl patch command accepts a filename param --- contrib/completions/bash/kubectl | 6 +++++ docs/man/man1/kubectl-patch.1 | 7 +++++ docs/user-guide/kubectl/kubectl_patch.md | 8 ++++-- hack/test-cmd.sh | 5 ++++ pkg/kubectl/cmd/patch.go | 33 +++++++++++++++--------- pkg/kubectl/cmd/patch_test.go | 33 ++++++++++++++++++++++++ 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 33c0c4d911e..bf1396e5407 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -371,6 +371,12 @@ _kubectl_patch() flags_with_completion=() flags_completion=() + flags+=("--filename=") + flags_with_completion+=("--filename") + flags_completion+=("__handle_filename_extension_flag json|yaml|yml") + two_word_flags+=("-f") + flags_with_completion+=("-f") + flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags+=("--help") flags+=("-h") flags+=("--output=") diff --git a/docs/man/man1/kubectl-patch.1 b/docs/man/man1/kubectl-patch.1 index 4737e4eb523..6e6cf10bbec 100644 --- a/docs/man/man1/kubectl-patch.1 +++ b/docs/man/man1/kubectl-patch.1 @@ -24,6 +24,10 @@ Please refer to the models in .SH OPTIONS +.PP +\fB\-f\fP, \fB\-\-filename\fP=[] + Filename, directory, or URL to a file identifying the resource to update + .PP \fB\-h\fP, \fB\-\-help\fP=false help for patch @@ -144,6 +148,9 @@ Please refer to the models in # Partially update a node using strategic merge patch kubectl patch node k8s\-node\-1 \-p '{"spec":{"unschedulable":true}}' +# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch +kubectl patch \-f node.json \-p '{"spec":{"unschedulable":true}}' + # Update a container's image; spec.containers[*].name is required because it's a merge key kubectl patch pod valid\-pod \-p '{"spec":{"containers":[{"name":"kubernetes\-serve\-hostname","image":"new image"}]}}' diff --git a/docs/user-guide/kubectl/kubectl_patch.md b/docs/user-guide/kubectl/kubectl_patch.md index b02d6f5b142..586c0e5cd2f 100644 --- a/docs/user-guide/kubectl/kubectl_patch.md +++ b/docs/user-guide/kubectl/kubectl_patch.md @@ -45,7 +45,7 @@ JSON and YAML formats are accepted. Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/HEAD/docs/api-reference/definitions.html to find if a field is mutable. ``` -kubectl patch TYPE NAME -p PATCH +kubectl patch (-f FILENAME | TYPE NAME) -p PATCH ``` ### Examples @@ -55,6 +55,9 @@ kubectl patch TYPE NAME -p PATCH # Partially update a node using strategic merge patch kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' +# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch +kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}' + # Update a container's image; spec.containers[*].name is required because it's a merge key kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}' ``` @@ -62,6 +65,7 @@ kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve ### Options ``` + -f, --filename=[]: Filename, directory, or URL to a file identifying the resource to update -h, --help[=false]: help for patch -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). -p, --patch="": The patch to be applied to the resource JSON file. @@ -100,7 +104,7 @@ kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-08-12 21:51:38.836855054 +0000 UTC +###### Auto generated by spf13/cobra at 2015-08-13 02:12:21.577994505 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_patch.md?pixel)]() diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 46e467596cf..4e1635ca7a6 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -385,6 +385,11 @@ runTests() { kubectl patch "${kube_flags[@]}" pod valid-pod -p='{"spec":{"containers":[{"name": "kubernetes-serve-hostname", "image": "nginx"}]}}' # Post-condition: valid-pod POD has image nginx kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:' + ## Patch pod from JSON can change image + # Command + kubectl patch "${kube_flags[@]}" -f docs/admin/limitrange/valid-pod.yaml -p='{"spec":{"containers":[{"name": "kubernetes-serve-hostname", "image": "kubernetes/pause"}]}}' + # Post-condition: valid-pod POD has image kubernetes/pause + kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'kubernetes/pause:' ## --force replace pod can change other field, e.g., spec.container.name # Command diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index ea9f6949b62..b24168cad30 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -17,11 +17,12 @@ limitations under the License. package cmd import ( + "fmt" "io" "github.com/spf13/cobra" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -36,13 +37,16 @@ Please refer to the models in https://htmlpreview.github.io/?https://github.com/ # Partially update a node using strategic merge patch kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' +# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch +kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}' + # Update a container's image; spec.containers[*].name is required because it's a merge key kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'` ) func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "patch TYPE NAME -p PATCH", + Use: "patch (-f FILENAME | TYPE NAME) -p PATCH", Short: "Update field(s) of a resource by stdin.", Long: patch_long, Example: patch_example, @@ -56,11 +60,14 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.MarkFlagRequired("patch") cmdutil.AddOutputFlagsForMutation(cmd) + + usage := "Filename, directory, or URL to a file identifying the resource to update" + kubectl.AddJsonFilenameFlag(cmd, usage) return cmd } func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool) error { - cmdNamespace, _, err := f.DefaultNamespace() + cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } @@ -74,6 +81,7 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). + FilenameParam(enforceNamespace, cmdutil.GetFlagStringSlice(cmd, "filename")...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() @@ -81,20 +89,21 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri if err != nil { return err } - mapping, err := r.ResourceMapping() - if err != nil { - return err - } - client, err := f.RESTClient(mapping) - if err != nil { - return err - } infos, err := r.Infos() if err != nil { return err } - name, namespace := infos[0].Name, infos[0].Namespace + if len(infos) > 1 { + return fmt.Errorf("multiple resources provided") + } + info := infos[0] + name, namespace := info.Name, info.Namespace + mapping := info.ResourceMapping() + client, err := f.RESTClient(mapping) + if err != nil { + return err + } helper := resource.NewHelper(client, mapping) _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, []byte(patch)) diff --git a/pkg/kubectl/cmd/patch_test.go b/pkg/kubectl/cmd/patch_test.go index 0cc5f93dfb3..133bd13dc93 100644 --- a/pkg/kubectl/cmd/patch_test.go +++ b/pkg/kubectl/cmd/patch_test.go @@ -55,3 +55,36 @@ func TestPatchObject(t *testing.T) { t.Errorf("unexpected output: %s", buf.String()) } } + +func TestPatchObjectFromFile(t *testing.T) { + _, svc, _ := testData() + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): + return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdPatch(f, buf) + cmd.Flags().Set("namespace", "test") + cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") + cmd.Run(cmd, []string{}) + + // uses the name from the file, not the response + if buf.String() != "frontend\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +}