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

Add --force to `kubectl update`
This commit is contained in:
Robert Bailey 2015-06-26 10:49:04 -07:00
commit 508f7f1697
9 changed files with 208 additions and 17 deletions

View File

@ -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=")

View File

@ -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
### 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)]()

View File

@ -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
### 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)]()

View File

@ -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=[]

View File

@ -20,10 +20,22 @@ JSON and YAML formats are accepted.
.SH OPTIONS .SH OPTIONS
.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 .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
\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 .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.
\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 \-
// 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

View File

@ -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'

View File

@ -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

View File

@ -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
$ 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 {
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
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
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
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
_, 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
}

View File

@ -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) {
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) {
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) {
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) {
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) {
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())
}
} }