Allow clients to determine the difference between create or update on PUT
PUT allows an object to be created (http 201). This allows REST code to indicate an object has been created and clients to react to it. APIServer now deals with <-chan RESTResult instead of <-chan runtime.Object, allowing more data to be passed through.
This commit is contained in:
@@ -57,6 +57,7 @@ type testClient struct {
|
||||
Request testRequest
|
||||
Response Response
|
||||
Error bool
|
||||
Created bool
|
||||
server *httptest.Server
|
||||
handler *util.FakeHandler
|
||||
// For query args, an optional function to validate the contents
|
||||
|
@@ -272,7 +272,7 @@ func (r *Request) Do() Result {
|
||||
if err != nil {
|
||||
return Result{err: err}
|
||||
}
|
||||
respBody, err := r.c.doRequest(req)
|
||||
respBody, created, err := r.c.doRequest(req)
|
||||
if err != nil {
|
||||
if s, ok := err.(APIStatus); ok {
|
||||
status := s.Status()
|
||||
@@ -292,14 +292,16 @@ func (r *Request) Do() Result {
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result{respBody, err, r.c.Codec}
|
||||
return Result{respBody, created, err, r.c.Codec}
|
||||
}
|
||||
}
|
||||
|
||||
// Result contains the result of calling Request.Do().
|
||||
type Result struct {
|
||||
body []byte
|
||||
err error
|
||||
body []byte
|
||||
created bool
|
||||
err error
|
||||
|
||||
codec runtime.Codec
|
||||
}
|
||||
|
||||
@@ -324,6 +326,13 @@ func (r Result) Into(obj runtime.Object) error {
|
||||
return r.codec.DecodeInto(r.body, obj)
|
||||
}
|
||||
|
||||
// WasCreated updates the provided bool pointer to whether the server returned
|
||||
// 201 created or a different response.
|
||||
func (r Result) WasCreated(wasCreated *bool) Result {
|
||||
*wasCreated = r.created
|
||||
return r
|
||||
}
|
||||
|
||||
// Error returns the error executing the request, nil if no error occurred.
|
||||
func (r Result) Error() error {
|
||||
return r.err
|
||||
|
@@ -168,13 +168,14 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||
wasCreated := true
|
||||
obj, err := c.Verb("POST").
|
||||
Path("foo/bar").
|
||||
Path("baz").
|
||||
ParseSelectorParam("labels", "name=foo").
|
||||
Timeout(time.Second).
|
||||
Body(file.Name()).
|
||||
Do().Get()
|
||||
Do().WasCreated(&wasCreated).Get()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v %#v", err, err)
|
||||
return
|
||||
@@ -184,6 +185,9 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
||||
} else if !reflect.DeepEqual(obj, expectedObj) {
|
||||
t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
|
||||
}
|
||||
if wasCreated {
|
||||
t.Errorf("expected object was not created")
|
||||
}
|
||||
tmpStr := string(reqBodyExpected)
|
||||
fakeHandler.ValidateRequest(t, "/api/v1beta1/foo/bar/baz?labels=name%3Dfoo", "POST", &tmpStr)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
@@ -191,6 +195,50 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWasCreated(t *testing.T) {
|
||||
reqObj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}
|
||||
reqBodyExpected, err := v1beta1.Codec.Encode(reqObj)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedObj := &api.Service{Port: 12345}
|
||||
expectedBody, _ := v1beta1.Codec.Encode(expectedObj)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 201,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||
wasCreated := false
|
||||
obj, err := c.Verb("PUT").
|
||||
Path("foo/bar").
|
||||
Path("baz").
|
||||
ParseSelectorParam("labels", "name=foo").
|
||||
Timeout(time.Second).
|
||||
Body(reqBodyExpected).
|
||||
Do().WasCreated(&wasCreated).Get()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v %#v", err, err)
|
||||
return
|
||||
}
|
||||
if obj == nil {
|
||||
t.Error("nil obj")
|
||||
} else if !reflect.DeepEqual(obj, expectedObj) {
|
||||
t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
|
||||
}
|
||||
if !wasCreated {
|
||||
t.Errorf("Expected object was created")
|
||||
}
|
||||
|
||||
tmpStr := string(reqBodyExpected)
|
||||
fakeHandler.ValidateRequest(t, "/api/v1beta1/foo/bar/baz?labels=name%3Dfoo", "PUT", &tmpStr)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *fakeHandler.RequestReceived)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerbs(t *testing.T) {
|
||||
c := NewOrDie(&Config{})
|
||||
if r := c.Post(); r.verb != "POST" {
|
||||
|
@@ -76,7 +76,7 @@ func NewRESTClient(baseURL *url.URL, c runtime.Codec) *RESTClient {
|
||||
}
|
||||
|
||||
// doRequest executes a request against a server
|
||||
func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
|
||||
func (c *RESTClient) doRequest(request *http.Request) ([]byte, bool, error) {
|
||||
client := c.Client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
@@ -84,12 +84,12 @@ func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return body, err
|
||||
return body, false, err
|
||||
}
|
||||
|
||||
// Did the server give us a status response?
|
||||
@@ -102,19 +102,20 @@ func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
|
||||
switch {
|
||||
case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
|
||||
if !isStatusResponse {
|
||||
return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
|
||||
return nil, false, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
|
||||
}
|
||||
return nil, errors.FromObject(&status)
|
||||
return nil, false, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
// If the server gave us a status back, look at what it was.
|
||||
if isStatusResponse && status.Status != api.StatusSuccess {
|
||||
// "Working" requests need to be handled specially.
|
||||
// "Failed" requests are clearly just an error and it makes sense to return them as such.
|
||||
return nil, errors.FromObject(&status)
|
||||
return nil, false, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
return body, err
|
||||
created := response.StatusCode == http.StatusCreated
|
||||
return body, created, err
|
||||
}
|
||||
|
||||
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
||||
|
@@ -105,6 +105,7 @@ func TestDoRequest(t *testing.T) {
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
testClients := []testClient{
|
||||
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
|
||||
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 201}, Created: true},
|
||||
{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
|
||||
{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
|
||||
{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
@@ -120,7 +121,10 @@ func TestDoRequest(t *testing.T) {
|
||||
Header: make(http.Header),
|
||||
URL: &prefix,
|
||||
}
|
||||
response, err := client.doRequest(request)
|
||||
response, created, err := client.doRequest(request)
|
||||
if c.Created != created {
|
||||
t.Errorf("expected created %f, got %f", c.Created, created)
|
||||
}
|
||||
//t.Logf("dorequest: %#v\n%#v\n%v", request.URL, response, err)
|
||||
c.ValidateRaw(t, response, err)
|
||||
}
|
||||
@@ -160,18 +164,16 @@ func TestDoRequestAccepted(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
body, _, err := c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Unexpected non-error")
|
||||
return
|
||||
t.Fatalf("Unexpected non-error")
|
||||
}
|
||||
se, ok := err.(APIStatus)
|
||||
if !ok {
|
||||
t.Errorf("Unexpected kind of error: %#v", err)
|
||||
return
|
||||
t.Fatalf("Unexpected kind of error: %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(se.Status(), *status) {
|
||||
t.Errorf("Unexpected status: %#v %#v", se.Status(), status)
|
||||
@@ -196,7 +198,7 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
body, _, err := c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
@@ -227,7 +229,7 @@ func TestDoRequestFailed(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, err := c.doRequest(request)
|
||||
body, _, err := c.doRequest(request)
|
||||
if err == nil || body != nil {
|
||||
t.Errorf("unexpected non-error: %#v", body)
|
||||
}
|
||||
@@ -240,3 +242,34 @@ func TestDoRequestFailed(t *testing.T) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestCreated(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusSuccess}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 201,
|
||||
ResponseBody: string(expectedBody),
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, created, err := c.doRequest(request)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if !created {
|
||||
t.Errorf("Expected object to be created")
|
||||
}
|
||||
statusOut, err := latest.Codec.Decode(body)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(status, statusOut) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, statusOut)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
}
|
||||
|
Reference in New Issue
Block a user