Commit 508f7f16 authored by Robert Bailey's avatar Robert Bailey

Merge pull request #10358 from caesarxuchao/add-force-update

Add --force to `kubectl update`
parents 54a3ebef ef5de91f
...@@ -331,15 +331,19 @@ _kubectl_update() ...@@ -331,15 +331,19 @@ _kubectl_update()
flags_with_completion=() flags_with_completion=()
flags_completion=() flags_completion=()
flags+=("--cascade")
flags+=("--filename=") flags+=("--filename=")
flags_with_completion+=("--filename") flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
two_word_flags+=("-f") two_word_flags+=("-f")
flags_with_completion+=("-f") flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--force")
flags+=("--grace-period=")
flags+=("--help") flags+=("--help")
flags+=("-h") flags+=("-h")
flags+=("--patch=") flags+=("--patch=")
flags+=("--timeout=")
must_have_one_flag=() must_have_one_flag=()
must_have_one_flag+=("--filename=") must_have_one_flag+=("--filename=")
......
...@@ -43,7 +43,7 @@ $ kubectl delete pods --all ...@@ -43,7 +43,7 @@ $ kubectl delete pods --all
``` ```
--all=false: [-all] to select all the specified resources. --all=false: [-all] to select all the specified resources.
--cascade=true: If true, cascade the delete resources managed by this resource (e.g. Pods created by a ReplicationController). Default true. --cascade=true: If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.
-f, --filename=[]: Filename, directory, or URL to a file containing the resource to delete. -f, --filename=[]: Filename, directory, or URL to a file containing the resource to delete.
--grace-period=-1: Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. --grace-period=-1: Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.
-h, --help=false: help for delete -h, --help=false: help for delete
...@@ -84,6 +84,6 @@ $ kubectl delete pods --all ...@@ -84,6 +84,6 @@ $ kubectl delete pods --all
### 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-06-03 18:21:01.053120485 +0000 UTC ###### Auto generated by spf13/cobra at 2015-06-26 00:15:55.835198432 +0000 UTC
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_delete.md?pixel)]() [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_delete.md?pixel)]()
...@@ -24,14 +24,21 @@ $ cat pod.json | kubectl update -f - ...@@ -24,14 +24,21 @@ $ cat pod.json | kubectl update -f -
// Partially update a node using strategic merge patch // Partially update a node using strategic merge patch
kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable":true}}' kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable":true}}'
// Force update, delete and then re-create the resource
kubectl update --force -f pod.json
``` ```
### Options ### Options
``` ```
--cascade=false: Only relevant during a force update. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.
-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.
--force=false: Delete and re-create the specified resource
--grace-period=-1: Only relevant during a force update. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.
-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. --patch="": A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.
--timeout=0: Only relevant during a force update. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
...@@ -66,6 +73,6 @@ kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable ...@@ -66,6 +73,6 @@ kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable
### 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-06-18 19:03:00.935576604 +0000 UTC ###### Auto generated by spf13/cobra at 2015-06-26 00:15:55.835055081 +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)]()
...@@ -35,7 +35,7 @@ will be lost along with the rest of the resource. ...@@ -35,7 +35,7 @@ will be lost along with the rest of the resource.
.PP .PP
\fB\-\-cascade\fP=true \fB\-\-cascade\fP=true
If true, cascade the delete resources managed by this resource (e.g. Pods created by a ReplicationController). Default true. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.
.PP .PP
\fB\-f\fP, \fB\-\-filename\fP=[] \fB\-f\fP, \fB\-\-filename\fP=[]
......
...@@ -21,10 +21,22 @@ JSON and YAML formats are accepted. ...@@ -21,10 +21,22 @@ JSON and YAML formats are accepted.
.SH OPTIONS .SH OPTIONS
.PP .PP
\fB\-\-cascade\fP=false
Only relevant during a force update. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.
.PP
\fB\-f\fP, \fB\-\-filename\fP=[] \fB\-f\fP, \fB\-\-filename\fP=[]
Filename, directory, or URL to file to use to update the resource. Filename, directory, or URL to file to use to update the resource.
.PP .PP
\fB\-\-force\fP=false
Delete and re\-create the specified resource
.PP
\fB\-\-grace\-period\fP=\-1
Only relevant during a force update. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.
.PP
\fB\-h\fP, \fB\-\-help\fP=false \fB\-h\fP, \fB\-\-help\fP=false
help for update help for update
...@@ -32,6 +44,10 @@ JSON and YAML formats are accepted. ...@@ -32,6 +44,10 @@ JSON and YAML formats are accepted.
\fB\-\-patch\fP="" \fB\-\-patch\fP=""
A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated. A JSON document to override the existing resource. The resource is downloaded, patched with the JSON, then updated.
.PP
\fB\-\-timeout\fP=0
Only relevant during a force update. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object
.SH OPTIONS INHERITED FROM PARENT COMMANDS .SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP .PP
...@@ -145,6 +161,9 @@ $ cat pod.json | kubectl update \-f \- ...@@ -145,6 +161,9 @@ $ cat pod.json | kubectl update \-f \-
// Partially update a node using strategic merge patch // Partially update a node using strategic merge patch
kubectl \-\-api\-version=v1 update node k8s\-node\-1 \-\-patch='\{"spec":\{"unschedulable":true\}\}' kubectl \-\-api\-version=v1 update node k8s\-node\-1 \-\-patch='\{"spec":\{"unschedulable":true\}\}'
// Force update, delete and then re\-create the resource
kubectl update \-\-force \-f pod.json
.fi .fi
.RE .RE
......
...@@ -387,6 +387,13 @@ for version in "${kube_api_versions[@]}"; do ...@@ -387,6 +387,13 @@ for version in "${kube_api_versions[@]}"; do
# Post-condition: valid-pod POD has image nginx # Post-condition: valid-pod POD has image nginx
kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:' kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:'
## --force update 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"/"update-k8s-serve-hostname"/g' > tmp-valid-pod.json
kubectl update "${kube_flags[@]}" --force -f tmp-valid-pod.json
# Post-condition: spec.container.name = "update-k8s-serve-hostname"
kube::test::get_object_assert 'pod valid-pod' "{{(index .spec.containers 0).name}}" 'update-k8s-serve-hostname'
### 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'
......
...@@ -75,7 +75,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { ...@@ -75,7 +75,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.") cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.")
cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.") cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.")
cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful delete.") cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful delete.")
cmd.Flags().Bool("cascade", true, "If true, cascade the delete resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.") cmd.Flags().Bool("cascade", true, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
return cmd return cmd
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
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"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
) )
const ( const (
...@@ -40,7 +41,10 @@ $ kubectl update -f pod.json ...@@ -40,7 +41,10 @@ $ kubectl update -f pod.json
$ cat pod.json | kubectl update -f - $ cat pod.json | kubectl update -f -
// Partially update a node using strategic merge patch // Partially update a node using strategic merge patch
kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable":true}}'` kubectl --api-version=v1 update node k8s-node-1 --patch='{"spec":{"unschedulable":true}}'
// Force update, delete and then re-create the resource
kubectl update --force -f pod.json`
) )
func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
...@@ -60,6 +64,10 @@ func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { ...@@ -60,6 +64,10 @@ func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.MarkFlagRequired("filename") cmd.MarkFlagRequired("filename")
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")
cmd.Flags().Bool("force", false, "Delete and re-create the specified resource")
cmd.Flags().Bool("cascade", false, "Only relevant during a force update. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
cmd.Flags().Int("grace-period", -1, "Only relevant during a force update. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Duration("timeout", 0, "Only relevant during a force update. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object")
return cmd return cmd
} }
...@@ -74,6 +82,7 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -74,6 +82,7 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err return err
} }
force := cmdutil.GetFlagBool(cmd, "force")
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")
...@@ -81,6 +90,9 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -81,6 +90,9 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
if len(filenames) != 0 && len(patch) != 0 { if len(filenames) != 0 && len(patch) != 0 {
return cmdutil.UsageError(cmd, "Can not specify both --filename and --patch") return cmdutil.UsageError(cmd, "Can not specify both --filename and --patch")
} }
if len(filenames) == 0 && force {
return cmdutil.UsageError(cmd, "--force can only be used with --filename")
}
// TODO: Make patching work with -f, updating with patched JSON input files // TODO: Make patching work with -f, updating with patched JSON input files
if len(filenames) == 0 { if len(filenames) == 0 {
...@@ -95,6 +107,10 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ...@@ -95,6 +107,10 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return cmdutil.UsageError(cmd, "Must specify --filename to update") return cmdutil.UsageError(cmd, "Must specify --filename to update")
} }
if force {
return forceUpdate(f, out, cmd, args, filenames)
}
mapper, typer := f.Object() mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
Schema(schema). Schema(schema).
...@@ -160,3 +176,76 @@ func updateWithPatch(cmd *cobra.Command, args []string, f *cmdutil.Factory, patc ...@@ -160,3 +176,76 @@ func updateWithPatch(cmd *cobra.Command, args []string, f *cmdutil.Factory, patc
_, err = helper.Patch(namespace, name, api.StrategicMergePatchType, []byte(patch)) _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, []byte(patch))
return name, err return name, err
} }
func forceUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error {
schema, err := f.Validator()
if err != nil {
return err
}
cmdNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(filenames...).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
//Update will create a resource if it doesn't exist already, so ignore not found error
ignoreNotFound := true
// By default use a reaper to delete all related resources.
if cmdutil.GetFlagBool(cmd, "cascade") {
glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.")
err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period"))
} else {
err = DeleteResult(r, out, ignoreNotFound)
}
if err != nil {
return err
}
r = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filenames...).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
count := 0
err = r.Visit(func(info *resource.Info) error {
data, err := info.Mapping.Codec.Encode(info.Object)
if err != nil {
return err
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, data)
if err != nil {
return err
}
count++
info.Refresh(obj, true)
printObjectSpecificMessage(obj, out)
fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name)
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to update")
}
return nil
}
...@@ -34,10 +34,10 @@ func TestUpdateObject(t *testing.T) { ...@@ -34,10 +34,10 @@ func TestUpdateObject(t *testing.T) {
Codec: codec, Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET": case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == "GET" || m == "PUT" || m == "DELETE"):
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "PUT":
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil
default: default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil return nil, nil
...@@ -55,6 +55,15 @@ func TestUpdateObject(t *testing.T) { ...@@ -55,6 +55,15 @@ func TestUpdateObject(t *testing.T) {
if buf.String() != "replicationcontrollers/rc1\n" { if buf.String() != "replicationcontrollers/rc1\n" {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
buf.Reset()
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontrollers/redis-master\nreplicationcontrollers/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
} }
func TestUpdateMultipleObject(t *testing.T) { func TestUpdateMultipleObject(t *testing.T) {
...@@ -66,14 +75,14 @@ func TestUpdateMultipleObject(t *testing.T) { ...@@ -66,14 +75,14 @@ func TestUpdateMultipleObject(t *testing.T) {
Codec: codec, Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET": case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == "GET" || m == "PUT" || m == "DELETE"):
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "PUT": case p == "/namespaces/test/replicationcontrollers" && m == "POST":
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/services/frontend" && m == "GET": case p == "/namespaces/test/services/frontend" && (m == "GET" || m == "PUT" || m == "DELETE"):
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/services/frontend" && m == "PUT":
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/services" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
default: default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil return nil, nil
...@@ -91,6 +100,15 @@ func TestUpdateMultipleObject(t *testing.T) { ...@@ -91,6 +100,15 @@ func TestUpdateMultipleObject(t *testing.T) {
if buf.String() != "replicationcontrollers/rc1\nservices/baz\n" { if buf.String() != "replicationcontrollers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
buf.Reset()
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontrollers/redis-master\nservices/frontend\nreplicationcontrollers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
} }
func TestUpdateDirectory(t *testing.T) { func TestUpdateDirectory(t *testing.T) {
...@@ -102,10 +120,14 @@ func TestUpdateDirectory(t *testing.T) { ...@@ -102,10 +120,14 @@ func TestUpdateDirectory(t *testing.T) {
Codec: codec, Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case strings.HasPrefix(p, "/namespaces/test/services/") && (m == "GET" || m == "PUT"): case strings.HasPrefix(p, "/namespaces/test/services/") && (m == "GET" || m == "PUT" || m == "DELETE"):
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == "GET" || m == "PUT"): case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == "GET" || m == "PUT" || m == "DELETE"):
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/services") && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers") && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil
default: default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil return nil, nil
...@@ -123,4 +145,47 @@ func TestUpdateDirectory(t *testing.T) { ...@@ -123,4 +145,47 @@ func TestUpdateDirectory(t *testing.T) {
if buf.String() != "replicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\n" { if buf.String() != "replicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
buf.Reset()
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontrollers/frontend\nservices/frontend\nreplicationcontrollers/redis-master\nservices/redis-master\nreplicationcontrollers/redis-slave\nservices/redis-slave\n"+
"replicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestForceUpdateObjectNotFound(t *testing.T) {
_, _, rc := 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/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.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 := NewCmdUpdate(f, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml")
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontrollers/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment