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