diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 6f4325185c7..41571e5cae9 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -47,7 +47,7 @@ Examples: client, err := f.Client(cmd, mapping) checkErr(err) - err = kubectl.NewRESTHelper(client, mapping).Create(namespace, data) + err = kubectl.NewRESTHelper(client, mapping).Create(namespace, true, data) checkErr(err) fmt.Fprintf(out, "%s\n", name) }, diff --git a/pkg/kubectl/resthelper.go b/pkg/kubectl/resthelper.go index e26f8dbc32c..48708263fa6 100644 --- a/pkg/kubectl/resthelper.go +++ b/pkg/kubectl/resthelper.go @@ -53,8 +53,37 @@ func (m *RESTHelper) Delete(namespace, name string) error { return m.RESTClient.Delete().Path(m.Resource).Namespace(namespace).Path(name).Do().Error() } -func (m *RESTHelper) Create(namespace string, data []byte) error { - return m.RESTClient.Post().Path(m.Resource).Namespace(namespace).Body(data).Do().Error() +func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error { + if modify { + obj, err := m.Codec.Decode(data) + if err != nil { + // We don't know how to check a version on this object, but create it anyway + return createResource(m.RESTClient, m.Resource, namespace, data) + } + + // Attempt to version the object based on client logic. + version, err := m.Versioner.ResourceVersion(obj) + if err != nil { + // We don't know how to clear the version on this object, so send it to the server as is + return createResource(m.RESTClient, m.Resource, namespace, data) + } + if version != "" { + if err := m.Versioner.SetResourceVersion(obj, ""); err != nil { + return err + } + newData, err := m.Codec.Encode(obj) + if err != nil { + return err + } + data = newData + } + } + + return createResource(m.RESTClient, m.Resource, namespace, data) +} + +func createResource(c RESTClient, resourcePath, namespace string, data []byte) error { + return c.Post().Path(resourcePath).Namespace(namespace).Body(data).Do().Error() } func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) error { diff --git a/pkg/kubectl/resthelper_test.go b/pkg/kubectl/resthelper_test.go index 4c8f8687d4a..42cb6b23a70 100644 --- a/pkg/kubectl/resthelper_test.go +++ b/pkg/kubectl/resthelper_test.go @@ -133,14 +133,28 @@ func TestRESTHelperDelete(t *testing.T) { } func TestRESTHelperCreate(t *testing.T) { - tests := []struct { - Resp *http.Response - HttpErr error - Object runtime.Object + expectPost := func(req *http.Request) bool { + if req.Method != "POST" { + t.Errorf("unexpected method: %#v", req) + return false + } + if req.URL.Query().Get("namespace") != "bar" { + t.Errorf("url doesn't contain namespace: %#v", req) + return false + } + return true + } - Err bool - Data []byte - Req func(*http.Request) bool + tests := []struct { + Resp *http.Response + RespFunc httpClientFunc + HttpErr error + Modify bool + Object runtime.Object + + ExpectObject runtime.Object + Err bool + Req func(*http.Request) bool }{ { HttpErr: errors.New("failure"), @@ -158,48 +172,65 @@ func TestRESTHelperCreate(t *testing.T) { StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess}), }, - Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, - Req: func(req *http.Request) bool { - if req.Method != "POST" { - t.Errorf("unexpected method: %#v", req) - return false - } - if req.URL.Query().Get("namespace") != "bar" { - t.Errorf("url doesn't contain namespace: %#v", req) - return false - } - return true - }, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + Req: expectPost, + }, + { + Modify: false, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, + Req: expectPost, + }, + { + Modify: true, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, + Req: expectPost, }, } - for _, test := range tests { + for i, test := range tests { client := &FakeRESTClient{ Resp: test.Resp, Err: test.HttpErr, } + if test.RespFunc != nil { + client.Client = test.RespFunc + } modifier := &RESTHelper{ RESTClient: client, + Codec: testapi.Codec(), + Versioner: testapi.MetadataAccessor(), } - data := test.Data + data := []byte{} if test.Object != nil { data = []byte(runtime.EncodeOrDie(testapi.Codec(), test.Object)) } - err := modifier.Create("bar", data) + err := modifier.Create("bar", test.Modify, data) if (err != nil) != test.Err { - t.Errorf("unexpected error: %f %v", test.Err, err) + t.Errorf("%d: unexpected error: %f %v", i, test.Err, err) } if err != nil { continue } if test.Req != nil && !test.Req(client.Req) { - t.Errorf("unexpected request: %#v", client.Req) + t.Errorf("%d: unexpected request: %#v", i, client.Req) } - if test.Data != nil { - body, _ := ioutil.ReadAll(client.Req.Body) - if !reflect.DeepEqual(test.Data, body) { - t.Errorf("unexpected body: %s", string(body)) - } + body, err := ioutil.ReadAll(client.Req.Body) + if err != nil { + t.Fatalf("%d: unexpected error: %#v", i, err) } + t.Logf("got body: %s", string(body)) + expect := []byte{} + if test.ExpectObject != nil { + expect = []byte(runtime.EncodeOrDie(testapi.Codec(), test.ExpectObject)) + } + if !reflect.DeepEqual(expect, body) { + t.Errorf("%d: unexpected body: %s", i, string(body)) + } + } } @@ -268,6 +299,22 @@ func TestRESTHelperGet(t *testing.T) { } func TestRESTHelperUpdate(t *testing.T) { + expectPut := func(req *http.Request) bool { + if req.Method != "PUT" { + t.Errorf("unexpected method: %#v", req) + return false + } + if !strings.HasSuffix(req.URL.Path, "/foo") { + t.Errorf("url doesn't contain name: %#v", req) + return false + } + if req.URL.Query().Get("namespace") != "bar" { + t.Errorf("url doesn't contain namespace: %#v", req) + return false + } + return true + } + tests := []struct { Resp *http.Response RespFunc httpClientFunc @@ -298,21 +345,7 @@ func TestRESTHelperUpdate(t *testing.T) { StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess}), }, - Req: func(req *http.Request) bool { - if req.Method != "PUT" { - t.Errorf("unexpected method: %#v", req) - return false - } - if !strings.HasSuffix(req.URL.Path, "/foo") { - t.Errorf("url doesn't contain name: %#v", req) - return false - } - if req.URL.Query().Get("namespace") != "bar" { - t.Errorf("url doesn't contain namespace: %#v", req) - return false - } - return true - }, + Req: expectPut, }, { Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, @@ -325,41 +358,13 @@ func TestRESTHelperUpdate(t *testing.T) { } return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil }, - Req: func(req *http.Request) bool { - if req.Method != "PUT" { - t.Errorf("unexpected method: %#v", req) - return false - } - if !strings.HasSuffix(req.URL.Path, "/foo") { - t.Errorf("url doesn't contain name: %#v", req) - return false - } - if req.URL.Query().Get("namespace") != "bar" { - t.Errorf("url doesn't contain namespace: %#v", req) - return false - } - return true - }, + Req: expectPut, }, { Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, - Req: func(req *http.Request) bool { - if req.Method != "PUT" { - t.Errorf("unexpected method: %#v", req) - return false - } - if !strings.HasSuffix(req.URL.Path, "/foo") { - t.Errorf("url doesn't contain name: %#v", req) - return false - } - if req.URL.Query().Get("namespace") != "bar" { - t.Errorf("url doesn't contain namespace: %#v", req) - return false - } - return true - }, + Req: expectPut, }, } for i, test := range tests {