Merge pull request #9993 from mikedanese/re-patch

reenable patch serverside using strategic-merge-patch
This commit is contained in:
Satnam Singh 2015-06-18 14:13:10 -07:00
commit d2c2f7e35e
9 changed files with 65 additions and 28 deletions

View File

@ -337,10 +337,12 @@ _kubectl_update()
flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help") flags+=("--help")
flags+=("-h") flags+=("-h")
flags+=("--patch=")
must_have_one_flag=() must_have_one_flag=()
must_have_one_flag+=("--filename=") must_have_one_flag+=("--filename=")
must_have_one_flag+=("-f") must_have_one_flag+=("-f")
must_have_one_flag+=("--patch=")
must_have_one_noun=() must_have_one_noun=()
} }

View File

@ -21,6 +21,9 @@ $ kubectl update -f pod.json
// Update a pod based on the JSON passed into stdin. // Update a pod based on the JSON passed into stdin.
$ cat pod.json | kubectl update -f - $ cat pod.json | kubectl update -f -
// Partially update a node using strategic merge patch
kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable":true}}'
``` ```
### Options ### Options
@ -28,6 +31,7 @@ $ cat pod.json | kubectl update -f -
``` ```
-f, --filename=[]: Filename, directory, or URL to file to use to update the resource. -f, --filename=[]: Filename, directory, or URL to file to use to update the resource.
-h, --help=false: help for update -h, --help=false: help for update
--patch="": A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
@ -62,6 +66,6 @@ $ cat pod.json | kubectl update -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-05-29 01:11:24.431126385 +0000 UTC ###### Auto generated by spf13/cobra at 2015-06-18 19:03:00.935576604 +0000 UTC
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_update.md?pixel)]() [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_update.md?pixel)]()

View File

@ -28,6 +28,10 @@ JSON and YAML formats are accepted.
\fB\-h\fP, \fB\-\-help\fP=false \fB\-h\fP, \fB\-\-help\fP=false
help for update help for update
.PP
\fB\-\-patch\fP=""
A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.
.SH OPTIONS INHERITED FROM PARENT COMMANDS .SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP .PP
@ -138,6 +142,9 @@ $ kubectl update \-f pod.json
// Update a pod based on the JSON passed into stdin. // Update a pod based on the JSON passed into stdin.
$ cat pod.json | kubectl update \-f \- $ cat pod.json | kubectl update \-f \-
// Partially update a node using strategic merge patch
kubectl \-\-api\-version=v1 update node k8s\-node\-1 \-\-patch='\{"spec":\{"unschedulable":true\}\}'
.fi .fi
.RE .RE

View File

@ -141,6 +141,7 @@ for version in "${kube_api_versions[@]}"; do
rc_status_replicas_field=".status.replicas" rc_status_replicas_field=".status.replicas"
rc_container_image_field=".spec.template.spec.containers" rc_container_image_field=".spec.template.spec.containers"
port_field="(index .spec.ports 0).port" port_field="(index .spec.ports 0).port"
image_field="(index .spec.containers 0).image"
# Passing no arguments to create is an error # Passing no arguments to create is an error
! kubectl create ! kubectl create
@ -306,6 +307,14 @@ for version in "${kube_api_versions[@]}"; do
# Post-condition: valid-pod POD is running # Post-condition: valid-pod POD is running
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'
## --patch update pod can change image
# Pre-condition: valid-pod POD is running
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'
# Command
kubectl update "${kube_flags[@]}" pod valid-pod --patch='{"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:'
### Overwriting an existing label is not permitted ### Overwriting an existing label is not permitted
# Pre-condition: name is valid-pod # Pre-condition: name is valid-pod
kube::test::get_object_assert 'pod valid-pod' "{{${labels_field}.name}}" 'valid-pod' kube::test::get_object_assert 'pod valid-pod' "{{${labels_field}.name}}" 'valid-pod'
@ -615,6 +624,15 @@ __EOF__
kube::test::describe_object_assert nodes "127.0.0.1" "Name:" "Labels:" "CreationTimestamp:" "Conditions:" "Addresses:" "Capacity:" "Pods:" kube::test::describe_object_assert nodes "127.0.0.1" "Name:" "Labels:" "CreationTimestamp:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
### --patch update can mark node unschedulable
# Pre-condition: node is schedulable
kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '<no value>'
kubectl update "${kube_flags[@]}" nodes "127.0.0.1" --patch='{"spec":{"unschedulable":true}}'
# Post-condition: node is unschedulable
kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" 'true'
kubectl update "${kube_flags[@]}" nodes "127.0.0.1" --patch='{"spec":{"unschedulable":null}}'
# Post-condition: node is schedulable
kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '<no value>'
########### ###########
# Nodes # # Nodes #

View File

@ -20,6 +20,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
) )
@ -47,6 +48,10 @@ func (c *FakeRESTClient) Put() *Request {
return NewRequest(c, "PUT", &url.URL{Host: "localhost"}, testapi.Version(), c.Codec) return NewRequest(c, "PUT", &url.URL{Host: "localhost"}, testapi.Version(), c.Codec)
} }
func (c *FakeRESTClient) Patch(_ api.PatchType) *Request {
return NewRequest(c, "PATCH", &url.URL{Host: "localhost"}, testapi.Version(), c.Codec)
}
func (c *FakeRESTClient) Post() *Request { func (c *FakeRESTClient) Post() *Request {
return NewRequest(c, "POST", &url.URL{Host: "localhost"}, testapi.Version(), c.Codec) return NewRequest(c, "POST", &url.URL{Host: "localhost"}, testapi.Version(), c.Codec)
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
@ -36,7 +37,10 @@ JSON and YAML formats are accepted.`
$ kubectl update -f pod.json $ kubectl update -f pod.json
// Update a pod based on the JSON passed into stdin. // Update a pod based on the JSON passed into stdin.
$ cat pod.json | kubectl update -f -` $ cat pod.json | kubectl update -f -
// Partially update a node using strategic merge patch
kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable":true}}'`
) )
func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
@ -54,9 +58,8 @@ func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
usage := "Filename, directory, or URL to file to use to update the resource." usage := "Filename, directory, or URL to file to use to update the resource."
kubectl.AddJsonFilenameFlag(cmd, &filenames, usage) kubectl.AddJsonFilenameFlag(cmd, &filenames, usage)
cmd.MarkFlagRequired("filename") cmd.MarkFlagRequired("filename")
// TODO: re-enable --patch and make it use strategic-merge-patch cmd.Flags().String("patch", "", "A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.")
// cmd.Flags().String("patch", "", "A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.") cmd.MarkFlagRequired("patch")
// cmd.MarkFlagRequired("patch")
return cmd return cmd
} }
@ -71,7 +74,7 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err return err
} }
/* patch := cmdutil.GetFlagString(cmd, "patch") patch := cmdutil.GetFlagString(cmd, "patch")
if len(filenames) == 0 && len(patch) == 0 { if len(filenames) == 0 && len(patch) == 0 {
return cmdutil.UsageError(cmd, "Must specify --filename or --patch to update") return cmdutil.UsageError(cmd, "Must specify --filename or --patch to update")
} }
@ -87,7 +90,7 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
} }
fmt.Fprintf(out, "%s\n", name) fmt.Fprintf(out, "%s\n", name)
return nil return nil
} */ }
if len(filenames) == 0 { if len(filenames) == 0 {
return cmdutil.UsageError(cmd, "Must specify --filename to update") return cmdutil.UsageError(cmd, "Must specify --filename to update")
} }
@ -154,21 +157,6 @@ func updateWithPatch(cmd *cobra.Command, args []string, f *cmdutil.Factory, patc
name, namespace := infos[0].Name, infos[0].Namespace name, namespace := infos[0].Name, infos[0].Namespace
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
obj, err := helper.Get(namespace, name) _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, []byte(patch))
if err != nil {
return "", err
}
patchedObj, err := cmdutil.Merge(obj, patch, mapping.Kind)
if err != nil {
return "", err
}
data, err := helper.Codec.Encode(patchedObj)
if err != nil {
return "", err
}
_, err = helper.Update(namespace, name, true, data)
return name, err return name, err
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package kubectl package kubectl
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
) )
@ -25,6 +26,7 @@ import (
type RESTClient interface { type RESTClient interface {
Get() *client.Request Get() *client.Request
Post() *client.Request Post() *client.Request
Patch(api.PatchType) *client.Request
Delete() *client.Request Delete() *client.Request
Put() *client.Request Put() *client.Request
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package resource package resource
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
@ -43,11 +44,10 @@ type Helper struct {
// NewHelper creates a Helper from a ResourceMapping // NewHelper creates a Helper from a ResourceMapping
func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper { func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
return &Helper{ return &Helper{
RESTClient: client, RESTClient: client,
Resource: mapping.Resource, Resource: mapping.Resource,
Codec: mapping.Codec, Codec: mapping.Codec,
Versioner: mapping.MetadataAccessor, Versioner: mapping.MetadataAccessor,
NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace, NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
} }
} }
@ -133,6 +133,15 @@ func (m *Helper) Create(namespace string, modify bool, data []byte) (runtime.Obj
func (m *Helper) createResource(c RESTClient, resource, namespace string, data []byte) (runtime.Object, error) { func (m *Helper) createResource(c RESTClient, resource, namespace string, data []byte) (runtime.Object, error) {
return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(data).Do().Get() return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(data).Do().Get()
} }
func (m *Helper) Patch(namespace, name string, pt api.PatchType, data []byte) (runtime.Object, error) {
return m.RESTClient.Patch(pt).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
Body(data).
Do().
Get()
}
func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) (runtime.Object, error) { func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) (runtime.Object, error) {
c := m.RESTClient c := m.RESTClient

View File

@ -17,6 +17,7 @@ limitations under the License.
package resource package resource
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
) )
@ -26,6 +27,7 @@ import (
type RESTClient interface { type RESTClient interface {
Get() *client.Request Get() *client.Request
Post() *client.Request Post() *client.Request
Patch(api.PatchType) *client.Request
Delete() *client.Request Delete() *client.Request
Put() *client.Request Put() *client.Request
} }