diff --git a/remote/http.go b/remote/http.go index 1063b9d13..3953dbaec 100644 --- a/remote/http.go +++ b/remote/http.go @@ -54,6 +54,12 @@ func (c *HTTPRemoteClient) GetState() (*RemoteStatePayload, error) { return nil, nil case http.StatusNotFound: return nil, nil + case http.StatusUnauthorized: + return nil, ErrRequireAuth + case http.StatusForbidden: + return nil, ErrInvalidAuth + case http.StatusInternalServerError: + return nil, ErrRemoteInternal default: return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) } @@ -124,6 +130,16 @@ func (c *HTTPRemoteClient) PutState(state []byte, force bool) error { switch resp.StatusCode { case http.StatusOK: return nil + case http.StatusConflict: + return ErrConflict + case http.StatusPreconditionFailed: + return ErrServerNewer + case http.StatusUnauthorized: + return ErrRequireAuth + case http.StatusForbidden: + return ErrInvalidAuth + case http.StatusInternalServerError: + return ErrRemoteInternal default: return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) } @@ -151,6 +167,12 @@ func (c *HTTPRemoteClient) DeleteState() error { return nil case http.StatusNotFound: return nil + case http.StatusUnauthorized: + return ErrRequireAuth + case http.StatusForbidden: + return ErrInvalidAuth + case http.StatusInternalServerError: + return ErrRemoteInternal default: return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) } diff --git a/remote/http_test.go b/remote/http_test.go index ce12e376b..ef23d9097 100644 --- a/remote/http_test.go +++ b/remote/http_test.go @@ -1,6 +1,16 @@ package remote -import "testing" +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/hashicorp/terraform/terraform" +) func TestHTTPRemote_Interface(t *testing.T) { var client interface{} = &HTTPRemoteClient{} @@ -9,6 +19,291 @@ func TestHTTPRemote_Interface(t *testing.T) { } } -func TestHTTPRemote(t *testing.T) { - // TODO +func TestHTTPRemote_GetState(t *testing.T) { + type tcase struct { + Code int + Header http.Header + Body []byte + ExpectMD5 []byte + ExpectErr string + } + inp := []byte("testing") + inpMD5 := md5.Sum(inp) + hash := inpMD5[:16] + cases := []*tcase{ + &tcase{ + Code: http.StatusOK, + Body: inp, + ExpectMD5: hash, + }, + &tcase{ + Code: http.StatusNoContent, + }, + &tcase{ + Code: http.StatusNotFound, + }, + &tcase{ + Code: http.StatusInternalServerError, + ExpectErr: "Remote server reporting internal error", + }, + &tcase{ + Code: 418, + ExpectErr: "Unexpected HTTP response code 418", + }, + } + + for _, tc := range cases { + cb := func(resp http.ResponseWriter, req *http.Request) { + for k, v := range tc.Header { + resp.Header()[k] = v + } + resp.WriteHeader(tc.Code) + if tc.Body != nil { + resp.Write(tc.Body) + } + } + s := httptest.NewServer(http.HandlerFunc(cb)) + defer s.Close() + + remote := &terraform.RemoteState{ + Type: "http", + Config: map[string]string{ + "url": s.URL, + }, + } + r, err := NewClientByState(remote) + if err != nil { + t.Fatalf("Err: %v", err) + } + + payload, err := r.GetState() + errStr := "" + if err != nil { + errStr = err.Error() + } + if errStr != tc.ExpectErr { + t.Fatalf("bad err: %v %v", errStr, tc.ExpectErr) + } + + if tc.ExpectMD5 != nil { + if payload == nil || !bytes.Equal(payload.MD5, tc.ExpectMD5) { + t.Fatalf("bad: %#v", payload) + } + } + + if tc.Body != nil { + if !bytes.Equal(payload.State, tc.Body) { + t.Fatalf("bad: %#v", payload) + } + } + } + +} + +func TestHTTPRemote_PutState(t *testing.T) { + type tcase struct { + Code int + Path string + Header http.Header + Body []byte + ExpectMD5 []byte + Force bool + ExpectErr string + } + inp := []byte("testing") + inpMD5 := md5.Sum(inp) + hash := inpMD5[:16] + cases := []*tcase{ + &tcase{ + Code: http.StatusOK, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + }, + &tcase{ + Code: http.StatusOK, + Path: "/foobar?force=true", + Body: inp, + Force: true, + ExpectMD5: hash, + }, + &tcase{ + Code: http.StatusConflict, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + ExpectErr: ErrConflict.Error(), + }, + &tcase{ + Code: http.StatusPreconditionFailed, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + ExpectErr: ErrServerNewer.Error(), + }, + &tcase{ + Code: http.StatusUnauthorized, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + ExpectErr: ErrRequireAuth.Error(), + }, + &tcase{ + Code: http.StatusForbidden, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + ExpectErr: ErrInvalidAuth.Error(), + }, + &tcase{ + Code: http.StatusInternalServerError, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + ExpectErr: ErrRemoteInternal.Error(), + }, + &tcase{ + Code: 418, + Path: "/foobar", + Body: inp, + ExpectMD5: hash, + ExpectErr: "Unexpected HTTP response code 418", + }, + } + + for _, tc := range cases { + cb := func(resp http.ResponseWriter, req *http.Request) { + for k, v := range tc.Header { + resp.Header()[k] = v + } + resp.WriteHeader(tc.Code) + + // Verify the body + buf := bytes.NewBuffer(nil) + io.Copy(buf, req.Body) + if !bytes.Equal(buf.Bytes(), tc.Body) { + t.Fatalf("bad body: %v", buf.Bytes()) + } + + // Verify the path + req.URL.Host = "" + if req.URL.String() != tc.Path { + t.Fatalf("Bad path: %v %v", req.URL.String(), tc.Path) + } + + // Verify the content length + if req.ContentLength != int64(len(tc.Body)) { + t.Fatalf("bad content length: %d", req.ContentLength) + } + + // Verify the Content-MD5 + b64 := req.Header.Get("Content-MD5") + raw, _ := base64.StdEncoding.DecodeString(b64) + if !bytes.Equal(raw, tc.ExpectMD5) { + t.Fatalf("bad md5: %v", raw) + } + } + s := httptest.NewServer(http.HandlerFunc(cb)) + defer s.Close() + + remote := &terraform.RemoteState{ + Type: "http", + Config: map[string]string{ + "url": s.URL + "/foobar", + }, + } + r, err := NewClientByState(remote) + if err != nil { + t.Fatalf("Err: %v", err) + } + + err = r.PutState(tc.Body, tc.Force) + errStr := "" + if err != nil { + errStr = err.Error() + } + if errStr != tc.ExpectErr { + t.Fatalf("bad err: %v %v", errStr, tc.ExpectErr) + } + } +} + +func TestHTTPRemote_DeleteState(t *testing.T) { + type tcase struct { + Code int + Path string + Header http.Header + ExpectErr string + } + cases := []*tcase{ + &tcase{ + Code: http.StatusOK, + Path: "/foobar", + }, + &tcase{ + Code: http.StatusNoContent, + Path: "/foobar", + }, + &tcase{ + Code: http.StatusNotFound, + Path: "/foobar", + }, + &tcase{ + Code: http.StatusUnauthorized, + Path: "/foobar", + ExpectErr: ErrRequireAuth.Error(), + }, + &tcase{ + Code: http.StatusForbidden, + Path: "/foobar", + ExpectErr: ErrInvalidAuth.Error(), + }, + &tcase{ + Code: http.StatusInternalServerError, + Path: "/foobar", + ExpectErr: ErrRemoteInternal.Error(), + }, + &tcase{ + Code: 418, + Path: "/foobar", + ExpectErr: "Unexpected HTTP response code 418", + }, + } + + for _, tc := range cases { + cb := func(resp http.ResponseWriter, req *http.Request) { + for k, v := range tc.Header { + resp.Header()[k] = v + } + resp.WriteHeader(tc.Code) + + // Verify the path + req.URL.Host = "" + if req.URL.String() != tc.Path { + t.Fatalf("Bad path: %v %v", req.URL.String(), tc.Path) + } + } + s := httptest.NewServer(http.HandlerFunc(cb)) + defer s.Close() + + remote := &terraform.RemoteState{ + Type: "http", + Config: map[string]string{ + "url": s.URL + "/foobar", + }, + } + r, err := NewClientByState(remote) + if err != nil { + t.Fatalf("Err: %v", err) + } + + err = r.DeleteState() + errStr := "" + if err != nil { + errStr = err.Error() + } + if errStr != tc.ExpectErr { + t.Fatalf("bad err: %v %v", errStr, tc.ExpectErr) + } + } }