diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 3380c2505f7..1ca375f6bf7 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -350,6 +350,27 @@ _kubectl_replace() must_have_one_noun=() } +_kubectl_patch() +{ + last_command="kubectl_patch" + commands=() + + flags=() + two_word_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--help") + flags+=("-h") + flags+=("--patch=") + two_word_flags+=("-p") + + must_have_one_flag=() + must_have_one_flag+=("--patch=") + must_have_one_flag+=("-p") + must_have_one_noun=() +} + _kubectl_delete() { last_command="kubectl_delete" @@ -905,6 +926,7 @@ _kubectl() commands+=("describe") commands+=("create") commands+=("replace") + commands+=("patch") commands+=("delete") commands+=("namespace") commands+=("logs") diff --git a/docs/.files_generated b/docs/.files_generated index d0bbd05b491..71996881a42 100644 --- a/docs/.files_generated +++ b/docs/.files_generated @@ -18,6 +18,7 @@ kubectl_get.md kubectl_label.md kubectl_logs.md kubectl_namespace.md +kubectl_patch.md kubectl_port-forward.md kubectl_proxy.md kubectl_replace.md diff --git a/docs/kubectl.md b/docs/kubectl.md index 8d5fc2b6bbc..3b36c9cd03b 100644 --- a/docs/kubectl.md +++ b/docs/kubectl.md @@ -56,6 +56,7 @@ kubectl * [kubectl label](kubectl_label.md) - Update the labels on a resource * [kubectl logs](kubectl_logs.md) - Print the logs for a container in a pod. * [kubectl namespace](kubectl_namespace.md) - SUPERCEDED: Set and view the current Kubernetes namespace +* [kubectl patch](kubectl_patch.md) - Update field(s) of a resource by stdin. * [kubectl port-forward](kubectl_port-forward.md) - Forward one or more local ports to a pod. * [kubectl proxy](kubectl_proxy.md) - Run a proxy to the Kubernetes API server * [kubectl replace](kubectl_replace.md) - Replace a resource by filename or stdin. @@ -65,6 +66,6 @@ kubectl * [kubectl stop](kubectl_stop.md) - Gracefully shut down a resource by id or filename. * [kubectl version](kubectl_version.md) - Print the client and server version information. -###### Auto generated by spf13/cobra at 2015-06-29 00:10:06.115525904 +0000 UTC +###### Auto generated by spf13/cobra at 2015-06-29 23:26:32.294787115 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl.md?pixel)]() diff --git a/docs/kubectl_patch.md b/docs/kubectl_patch.md new file mode 100644 index 00000000000..b8683a8f07a --- /dev/null +++ b/docs/kubectl_patch.md @@ -0,0 +1,65 @@ +## kubectl patch + +Update field(s) of a resource by stdin. + +### Synopsis + + +Update field(s) of a resource using strategic merge patch + +JSON and YAML formats are accepted. + +``` +kubectl patch RESOURCE NAME -p PATCH +``` + +### Examples + +``` + +// Partially update a node using strategic merge patch +kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' +``` + +### Options + +``` + -h, --help=false: help for patch + -p, --patch="": The patch to be appied to the resource JSON file. +``` + +### Options inherited from parent commands + +``` + --alsologtostderr=false: log to standard error as well as files + --api-version="": The API version to use when talking to the server + --certificate-authority="": Path to a cert. file for the certificate authority. + --client-certificate="": Path to a client key file for TLS. + --client-key="": Path to a client key file for TLS. + --cluster="": The name of the kubeconfig cluster to use + --context="": The name of the kubeconfig context to use + --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace + --log-dir=: If non-empty, write log files in this directory + --log-flush-frequency=5s: Maximum number of seconds between log flushes + --logtostderr=true: log to standard error instead of files + --match-server-version=false: Require server version to match client version + --namespace="": If present, the namespace scope for this CLI request. + --password="": Password for basic authentication to the API server. + -s, --server="": The address and port of the Kubernetes API server + --stderrthreshold=2: logs at or above this threshold go to stderr + --token="": Bearer token for authentication to the API server. + --user="": The name of the kubeconfig user to use + --username="": Username for basic authentication to the API server. + --v=0: log level for V logs + --validate=false: If true, use a schema to validate the input before sending it + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO +* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager + +###### Auto generated by spf13/cobra at 2015-06-26 01:21:14.985174217 +0000 UTC + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_patch.md?pixel)]() diff --git a/docs/man/man1/.files_generated b/docs/man/man1/.files_generated index cdadfebb693..4156725b069 100644 --- a/docs/man/man1/.files_generated +++ b/docs/man/man1/.files_generated @@ -17,6 +17,7 @@ kubectl-get.1 kubectl-label.1 kubectl-logs.1 kubectl-namespace.1 +kubectl-patch.1 kubectl-port-forward.1 kubectl-proxy.1 kubectl-replace.1 diff --git a/docs/man/man1/kubectl-patch.1 b/docs/man/man1/kubectl-patch.1 new file mode 100644 index 00000000000..60bc000f6ed --- /dev/null +++ b/docs/man/man1/kubectl-patch.1 @@ -0,0 +1,150 @@ +.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" "" + + +.SH NAME +.PP +kubectl patch \- Update field(s) of a resource by stdin. + + +.SH SYNOPSIS +.PP +\fBkubectl patch\fP [OPTIONS] + + +.SH DESCRIPTION +.PP +Update field(s) of a resource using strategic merge patch + +.PP +JSON and YAML formats are accepted. + + +.SH OPTIONS +.PP +\fB\-h\fP, \fB\-\-help\fP=false + help for patch + +.PP +\fB\-p\fP, \fB\-\-patch\fP="" + The patch to be appied to the resource JSON file. + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-alsologtostderr\fP=false + log to standard error as well as files + +.PP +\fB\-\-api\-version\fP="" + The API version to use when talking to the server + +.PP +\fB\-\-certificate\-authority\fP="" + Path to a cert. file for the certificate authority. + +.PP +\fB\-\-client\-certificate\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-client\-key\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-cluster\fP="" + The name of the kubeconfig cluster to use + +.PP +\fB\-\-context\fP="" + The name of the kubeconfig context to use + +.PP +\fB\-\-insecure\-skip\-tls\-verify\fP=false + If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + +.PP +\fB\-\-kubeconfig\fP="" + Path to the kubeconfig file to use for CLI requests. + +.PP +\fB\-\-log\-backtrace\-at\fP=:0 + when logging hits line file:N, emit a stack trace + +.PP +\fB\-\-log\-dir\fP="" + If non\-empty, write log files in this directory + +.PP +\fB\-\-log\-flush\-frequency\fP=5s + Maximum number of seconds between log flushes + +.PP +\fB\-\-logtostderr\fP=true + log to standard error instead of files + +.PP +\fB\-\-match\-server\-version\fP=false + Require server version to match client version + +.PP +\fB\-\-namespace\fP="" + If present, the namespace scope for this CLI request. + +.PP +\fB\-\-password\fP="" + Password for basic authentication to the API server. + +.PP +\fB\-s\fP, \fB\-\-server\fP="" + The address and port of the Kubernetes API server + +.PP +\fB\-\-stderrthreshold\fP=2 + logs at or above this threshold go to stderr + +.PP +\fB\-\-token\fP="" + Bearer token for authentication to the API server. + +.PP +\fB\-\-user\fP="" + The name of the kubeconfig user to use + +.PP +\fB\-\-username\fP="" + Username for basic authentication to the API server. + +.PP +\fB\-\-v\fP=0 + log level for V logs + +.PP +\fB\-\-validate\fP=false + If true, use a schema to validate the input before sending it + +.PP +\fB\-\-vmodule\fP= + comma\-separated list of pattern=N settings for file\-filtered logging + + +.SH EXAMPLE +.PP +.RS + +.nf + +// Partially update a node using strategic merge patch +kubectl patch node k8s\-node\-1 \-p '\{"spec":\{"unschedulable":true\}\}' + +.fi +.RE + + +.SH SEE ALSO +.PP +\fBkubectl(1)\fP, + + +.SH HISTORY +.PP +January 2015, Originally compiled by Eric Paris (eparis at redhat dot com) based on the kubernetes source material, but hopefully they have been automatically generated since! diff --git a/docs/man/man1/kubectl.1 b/docs/man/man1/kubectl.1 index 1766709b021..f6969202a60 100644 --- a/docs/man/man1/kubectl.1 +++ b/docs/man/man1/kubectl.1 @@ -124,7 +124,7 @@ Find more information at .SH SEE ALSO .PP -\fBkubectl\-get(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, +\fBkubectl\-get(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, .SH HISTORY diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index a6766420001..30a0de46d8f 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -378,12 +378,19 @@ for version in "${kube_api_versions[@]}"; do # Post-condition: valid-pod POD is running kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' + ## Patch pod can change image + # Command + 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:' + ## --force replace pod can change other field, e.g., spec.container.name # Command kubectl get "${kube_flags[@]}" pod valid-pod -o json | sed 's/"kubernetes-serve-hostname"/"replaced-k8s-serve-hostname"/g' > tmp-valid-pod.json kubectl replace "${kube_flags[@]}" --force -f tmp-valid-pod.json # Post-condition: spec.container.name = "replaced-k8s-serve-hostname" kube::test::get_object_assert 'pod valid-pod' "{{(index .spec.containers 0).name}}" 'replaced-k8s-serve-hostname' + rm tmp-valid-pod.json ### Overwriting an existing label is not permitted # Pre-condition: name is valid-pod @@ -718,6 +725,16 @@ __EOF__ kube::test::describe_object_assert nodes "127.0.0.1" "Name:" "Labels:" "CreationTimestamp:" "Conditions:" "Addresses:" "Capacity:" "Pods:" + ### kubectl patch update can mark node unschedulable + # Pre-condition: node is schedulable + kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' + kubectl patch "${kube_flags[@]}" nodes "127.0.0.1" -p='{"spec":{"unschedulable":true}}' + # Post-condition: node is unschedulable + kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" 'true' + kubectl patch "${kube_flags[@]}" nodes "127.0.0.1" -p='{"spec":{"unschedulable":null}}' + # Post-condition: node is schedulable + kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' + ########### # Nodes # ########### diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index f14dc63ea81..45b5c83d4ad 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -115,6 +115,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.AddCommand(NewCmdDescribe(f, out)) cmds.AddCommand(NewCmdCreate(f, out)) cmds.AddCommand(NewCmdReplace(f, out)) + cmds.AddCommand(NewCmdPatch(f, out)) cmds.AddCommand(NewCmdDelete(f, out)) cmds.AddCommand(NewCmdNamespace(out)) diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go new file mode 100644 index 00000000000..3fd08e6b794 --- /dev/null +++ b/pkg/kubectl/cmd/patch.go @@ -0,0 +1,99 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" +) + +const ( + patch_long = `Update field(s) of a resource using strategic merge patch + +JSON and YAML formats are accepted.` + patch_example = ` +// Partially update a node using strategic merge patch +kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'` +) + +func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "patch RESOURCE NAME -p PATCH", + Short: "Update field(s) of a resource by stdin.", + Long: patch_long, + Example: patch_example, + Run: func(cmd *cobra.Command, args []string) { + err := RunPatch(f, out, cmd, args) + cmdutil.CheckCustomErr("Patch failed", err) + }, + } + cmd.Flags().StringP("patch", "p", "", "The patch to be appied to the resource JSON file.") + cmd.MarkFlagRequired("patch") + return cmd +} + +func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { + cmdNamespace, err := f.DefaultNamespace() + if err != nil { + return err + } + + patch := cmdutil.GetFlagString(cmd, "patch") + if len(patch) == 0 { + return cmdutil.UsageError(cmd, "Must specify -p to patch") + } + + mapper, typer := f.Object() + r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + ContinueOnError(). + NamespaceParam(cmdNamespace).DefaultNamespace(). + ResourceTypeOrNameArgs(false, args...). + Flatten(). + Do() + err = r.Err() + 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 + + helper := resource.NewHelper(client, mapping) + _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, []byte(patch)) + if err != nil { + return err + } + fmt.Fprintf(out, "%s\n", name) + return nil +} diff --git a/pkg/kubectl/cmd/patch_test.go b/pkg/kubectl/cmd/patch_test.go new file mode 100644 index 00000000000..3206caef669 --- /dev/null +++ b/pkg/kubectl/cmd/patch_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "net/http" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" +) + +func TestPatchObject(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.Run(cmd, []string{"services/frontend"}) + + // uses the name from the file, not the response + if buf.String() != "frontend\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +}