Commit 322a6de1 authored by Adam Reese's avatar Adam Reese

Merge pull request #419 from adamreese/ref/client-resp-gh

ref(client): add helper methods to client
parents 1f40bd56 96c21b02
...@@ -94,7 +94,7 @@ func addRepo(c *cli.Context) error { ...@@ -94,7 +94,7 @@ func addRepo(c *cli.Context) error {
client := client.NewClient(dmURL) client := client.NewClient(dmURL)
var dest string = "" var dest string = ""
err := client.CallService(chartRepoPath, "POST", "add a chart repository", &dest, nil) _, err := client.Post(chartRepoPath, nil, &dest)
if err != nil { if err != nil {
return err return err
} }
...@@ -105,7 +105,7 @@ func addRepo(c *cli.Context) error { ...@@ -105,7 +105,7 @@ func addRepo(c *cli.Context) error {
func listRepos(c *cli.Context) error { func listRepos(c *cli.Context) error {
client := client.NewClient(dmURL) client := client.NewClient(dmURL)
var dest string = "" var dest string = ""
err := client.CallService(chartRepoPath, "GET", "list chart repos", &dest, nil) _, err := client.Get(chartRepoPath, &dest)
if err != nil { if err != nil {
return err return err
} }
...@@ -120,7 +120,7 @@ func removeRepo(c *cli.Context) error { ...@@ -120,7 +120,7 @@ func removeRepo(c *cli.Context) error {
} }
client := client.NewClient(dmURL) client := client.NewClient(dmURL)
var dest string = "" var dest string = ""
err := client.CallService(chartRepoPath, "DELETE", "delete a chart repository from list", &dest, nil) _, err := client.Delete(chartRepoPath, &dest)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package client package client
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
...@@ -29,11 +30,12 @@ import ( ...@@ -29,11 +30,12 @@ import (
"github.com/kubernetes/helm/pkg/version" "github.com/kubernetes/helm/pkg/version"
) )
// DefaultHTTPTimeout is the default HTTP timeout. const (
var DefaultHTTPTimeout = time.Second * 10 // DefaultHTTPTimeout is the default HTTP timeout.
DefaultHTTPTimeout = time.Second * 10
// DefaultHTTPProtocol is the default HTTP Protocol (http, https). // DefaultHTTPProtocol is the default HTTP Protocol (http, https).
var DefaultHTTPProtocol = "http" DefaultHTTPProtocol = "http"
)
// Client is a DM client. // Client is a DM client.
type Client struct { type Client struct {
...@@ -43,7 +45,6 @@ type Client struct { ...@@ -43,7 +45,6 @@ type Client struct {
Transport http.RoundTripper Transport http.RoundTripper
// Debug enables http logging // Debug enables http logging
Debug bool Debug bool
// Base URL for remote service // Base URL for remote service
baseURL *url.URL baseURL *url.URL
} }
...@@ -98,55 +99,83 @@ func (c *Client) agent() string { ...@@ -98,55 +99,83 @@ func (c *Client) agent() string {
return fmt.Sprintf("helm/%s", version.Version) return fmt.Sprintf("helm/%s", version.Version)
} }
// CallService is a low-level function for making an API call. // Get calls GET on an endpoint and decodes the response
// func (c *Client) Get(endpoint string, v interface{}) (*Response, error) {
// This calls the service and then unmarshals the returned data into dest. return c.Exec(c.NewRequest("GET", endpoint, nil), &v)
func (c *Client) CallService(path, method, action string, dest interface{}, reader io.Reader) error { }
u, err := c.url(path)
if err != nil { // Post calls POST on an endpoint and decodes the response
return err func (c *Client) Post(endpoint string, payload, v interface{}) (*Response, error) {
} return c.Exec(c.NewRequest("POST", endpoint, payload), &v)
}
resp, err := c.callHTTP(u, method, action, reader) // Delete calls DELETE on an endpoint and decodes the response
func (c *Client) Delete(endpoint string, v interface{}) (*Response, error) {
return c.Exec(c.NewRequest("DELETE", endpoint, nil), &v)
}
// NewRequest creates a new client request
func (c *Client) NewRequest(method, endpoint string, payload interface{}) *Request {
u, err := c.url(endpoint)
if err != nil { if err != nil {
return err return &Request{error: err}
} }
if err := json.Unmarshal([]byte(resp), dest); err != nil {
return fmt.Errorf("Failed to parse JSON response from service: %s", resp)
}
return nil
}
// callHTTP is a low-level primitive for executing HTTP operations. body := prepareBody(payload)
func (c *Client) callHTTP(path, method, action string, reader io.Reader) (string, error) { req, err := http.NewRequest(method, u, body)
request, err := http.NewRequest(method, path, reader)
// TODO: dynamically set version req.Header.Set("User-Agent", c.agent())
request.Header.Set("User-Agent", c.agent()) req.Header.Set("Accept", "application/json")
request.Header.Add("Content-Type", "application/json")
client := &http.Client{ // TODO: set Content-Type based on body
Timeout: c.HTTPTimeout, req.Header.Add("Content-Type", "application/json")
Transport: c.transport(),
}
response, err := client.Do(request) return &Request{req, err}
if err != nil { }
return "", err
func prepareBody(payload interface{}) io.Reader {
var body io.Reader
switch t := payload.(type) {
default:
//FIXME: panic is only for development
panic(fmt.Sprintf("unexpected type %T\n", t))
case io.Reader:
body = t
case []byte:
body = bytes.NewBuffer(t)
case nil:
} }
return body
}
defer response.Body.Close() // Exec sends a request and decodes the response
body, err := ioutil.ReadAll(response.Body) func (c *Client) Exec(req *Request, v interface{}) (*Response, error) {
if err != nil { return c.Result(c.Do(req), &v)
return "", err }
// Result checks status code and decodes a response body
func (c *Client) Result(resp *Response, v interface{}) (*Response, error) {
switch {
case resp.error != nil:
return resp, resp
case !resp.Success():
return resp, resp.HTTPError()
} }
return resp, decodeResponse(resp, v)
}
s := response.StatusCode // Do send a request and returns a response
if s < http.StatusOK || s >= http.StatusMultipleChoices { func (c *Client) Do(req *Request) *Response {
return "", &HTTPError{StatusCode: s, Message: string(body), URL: request.URL} if req.error != nil {
return &Response{error: req}
} }
return string(body), nil client := &http.Client{
Timeout: c.HTTPTimeout,
Transport: c.transport(),
}
resp, err := client.Do(req.Request)
return &Response{resp, err}
} }
// DefaultServerURL converts a host, host:port, or URL string to the default base server API path // DefaultServerURL converts a host, host:port, or URL string to the default base server API path
...@@ -173,6 +202,37 @@ func DefaultServerURL(host string) (*url.URL, error) { ...@@ -173,6 +202,37 @@ func DefaultServerURL(host string) (*url.URL, error) {
return hostURL, nil return hostURL, nil
} }
// Request wraps http.Request to include error
type Request struct {
*http.Request
error
}
// Response wraps http.Response to include error
type Response struct {
*http.Response
error
}
// Success returns true if the status code is 2xx
func (r *Response) Success() bool {
return r.StatusCode >= 200 && r.StatusCode < 300
}
// HTTPError creates a new HTTPError from response
func (r *Response) HTTPError() error {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
return &HTTPError{
StatusCode: r.StatusCode,
Message: string(body),
URL: r.Request.URL,
}
}
// HTTPError is an error caused by an unexpected HTTP status code. // HTTPError is an error caused by an unexpected HTTP status code.
// //
// The StatusCode will not necessarily be a 4xx or 5xx. Any unexpected code // The StatusCode will not necessarily be a 4xx or 5xx. Any unexpected code
...@@ -192,3 +252,14 @@ func (e *HTTPError) Error() string { ...@@ -192,3 +252,14 @@ func (e *HTTPError) Error() string {
func (e *HTTPError) String() string { func (e *HTTPError) String() string {
return e.Error() return e.Error()
} }
func decodeResponse(resp *Response, v interface{}) error {
defer resp.Body.Close()
if resp.Body == nil {
return nil
}
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
return fmt.Errorf("Failed to parse JSON response from service")
}
return nil
}
...@@ -102,5 +102,6 @@ func TestUserAgent(t *testing.T) { ...@@ -102,5 +102,6 @@ func TestUserAgent(t *testing.T) {
} }
}), }),
} }
fc.setup().ListDeployments() var nop struct{}
fc.setup().Get("/", &nop)
} }
...@@ -17,7 +17,6 @@ limitations under the License. ...@@ -17,7 +17,6 @@ limitations under the License.
package client package client
import ( import (
"bytes"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -32,11 +31,8 @@ import ( ...@@ -32,11 +31,8 @@ import (
// ListDeployments lists the deployments in DM. // ListDeployments lists the deployments in DM.
func (c *Client) ListDeployments() ([]string, error) { func (c *Client) ListDeployments() ([]string, error) {
var l []string var l []string
if err := c.CallService("deployments", "GET", "list deployments", &l, nil); err != nil { _, err := c.Get("deployments", &l)
return nil, err return l, err
}
return l, nil
} }
// PostChart sends a chart to DM for deploying. // PostChart sends a chart to DM for deploying.
...@@ -94,19 +90,15 @@ func (c *Client) PostChart(filename, deployname string) (string, error) { ...@@ -94,19 +90,15 @@ func (c *Client) PostChart(filename, deployname string) (string, error) {
// GetDeployment retrieves the supplied deployment // GetDeployment retrieves the supplied deployment
func (c *Client) GetDeployment(name string) (*common.Deployment, error) { func (c *Client) GetDeployment(name string) (*common.Deployment, error) {
var deployment *common.Deployment var deployment *common.Deployment
if err := c.CallService(fancypath.Join("deployments", name), "GET", "get deployment", &deployment, nil); err != nil { _, err := c.Get(fancypath.Join("deployments", name), &deployment)
return nil, err return deployment, err
}
return deployment, nil
} }
// DeleteDeployment deletes the supplied deployment // DeleteDeployment deletes the supplied deployment
func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) { func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) {
var deployment *common.Deployment var deployment *common.Deployment
if err := c.CallService(filepath.Join("deployments", name), "DELETE", "delete deployment", &deployment, nil); err != nil { _, err := c.Delete(filepath.Join("deployments", name), &deployment)
return nil, err return deployment, err
}
return deployment, nil
} }
// PostDeployment posts a deployment object to the manager service. // PostDeployment posts a deployment object to the manager service.
...@@ -127,7 +119,6 @@ func (c *Client) PostDeployment(name string, cfg *common.Configuration) error { ...@@ -127,7 +119,6 @@ func (c *Client) PostDeployment(name string, cfg *common.Configuration) error {
} }
var out struct{} var out struct{}
_, err = c.Post("/deployments", data, &out)
b := bytes.NewBuffer(data) return err
return c.CallService("/deployments", "POST", "post deployment", &out, b)
} }
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