Merge pull request #384 from adamreese/ref/client-resp

ref(client): separate deployments from client file
pull/387/head
Adam Reese 9 years ago
commit 759d80dc0a

@ -17,21 +17,16 @@ limitations under the License.
package client package client
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
fancypath "path"
"path/filepath"
"strings" "strings"
"time" "time"
"github.com/ghodss/yaml" "github.com/kubernetes/helm/pkg/version"
"github.com/kubernetes/helm/pkg/common"
) )
// DefaultHTTPTimeout is the default HTTP timeout. // DefaultHTTPTimeout is the default HTTP timeout.
@ -44,10 +39,6 @@ var DefaultHTTPProtocol = "http"
type Client struct { type Client struct {
// Timeout on HTTP connections. // Timeout on HTTP connections.
HTTPTimeout time.Duration HTTPTimeout time.Duration
// The remote host
Host string
// The protocol. Currently only http and https are supported.
Protocol string
// Transport // Transport
Transport http.RoundTripper Transport http.RoundTripper
// Debug enables http logging // Debug enables http logging
@ -90,7 +81,7 @@ func (c *Client) SetTransport(tr http.RoundTripper) *Client {
// SetTimeout sets a timeout for http connections // SetTimeout sets a timeout for http connections
func (c *Client) SetTimeout(seconds int) *Client { func (c *Client) SetTimeout(seconds int) *Client {
c.HTTPTimeout = time.Duration(time.Duration(seconds) * time.Second) c.HTTPTimeout = time.Duration(seconds) * time.Second
return c return c
} }
@ -104,7 +95,7 @@ func (c *Client) url(rawurl string) (string, error) {
} }
func (c *Client) agent() string { func (c *Client) agent() string {
return fmt.Sprintf("helm/%s", "0.0.1") return fmt.Sprintf("helm/%s", version.Version)
} }
// CallService is a low-level function for making an API call. // CallService is a low-level function for making an API call.
@ -182,68 +173,6 @@ func DefaultServerURL(host string) (*url.URL, error) {
return hostURL, nil return hostURL, nil
} }
// 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
}
// PostChart sends a chart to DM for deploying.
//
// This returns the location for the new chart, typically of the form
// `helm:repo/bucket/name-version.tgz`.
func (c *Client) PostChart(filename, deployname string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
u, err := c.url("/v2/charts")
request, err := http.NewRequest("POST", u, f)
if err != nil {
f.Close()
return "", err
}
// There is an argument to be made for using the legacy x-octet-stream for
// this. But since we control both sides, we should use the standard one.
// Also, gzip (x-compress) is usually treated as a content encoding. In this
// case it probably is not, but it makes more sense to follow the standard,
// even though we don't assume the remote server will strip it off.
request.Header.Add("Content-Type", "application/x-tar")
request.Header.Add("Content-Encoding", "gzip")
request.Header.Add("X-Deployment-Name", deployname)
request.Header.Add("X-Chart-Name", filepath.Base(filename))
request.Header.Set("User-Agent", c.agent())
client := &http.Client{
Timeout: c.HTTPTimeout,
Transport: c.transport(),
}
response, err := client.Do(request)
if err != nil {
return "", err
}
// We only want 201 CREATED. Admittedly, we could accept 200 and 202.
if response.StatusCode != http.StatusCreated {
body, err := ioutil.ReadAll(response.Body)
response.Body.Close()
if err != nil {
return "", err
}
return "", &HTTPError{StatusCode: response.StatusCode, Message: string(body), URL: request.URL}
}
loc := response.Header.Get("Location")
return loc, nil
}
// 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
@ -263,44 +192,3 @@ func (e *HTTPError) Error() string {
func (e *HTTPError) String() string { func (e *HTTPError) String() string {
return e.Error() return e.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
}
// 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
}
// PostDeployment posts a deployment object to the manager service.
func (c *Client) PostDeployment(name string, cfg *common.Configuration) error {
d, err := yaml.Marshal(cfg)
if err != nil {
return err
}
// This is a stop-gap until we get this API cleaned up.
t := common.Template{
Name: name,
Content: string(d),
}
data, err := json.Marshal(t)
if err != nil {
return err
}
var out struct{}
b := bytes.NewBuffer(data)
return c.CallService("/deployments", "POST", "post deployment", &out, b)
}

@ -17,13 +17,10 @@ limitations under the License.
package client package client
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"testing" "testing"
"github.com/kubernetes/helm/pkg/common"
) )
func TestDefaultServerURL(t *testing.T) { func TestDefaultServerURL(t *testing.T) {
@ -107,69 +104,3 @@ func TestUserAgent(t *testing.T) {
} }
fc.setup().ListDeployments() fc.setup().ListDeployments()
} }
func TestListDeployments(t *testing.T) {
fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`["guestbook.yaml"]`))
}),
}
defer fc.teardown()
l, err := fc.setup().ListDeployments()
if err != nil {
t.Fatal(err)
}
if len(l) != 1 {
t.Fatal("expected a single deployment")
}
}
func TestGetDeployment(t *testing.T) {
fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"name":"guestbook.yaml","id":0,"createdAt":"2016-02-08T12:17:49.251658308-08:00","deployedAt":"2016-02-08T12:17:49.251658589-08:00","modifiedAt":"2016-02-08T12:17:51.177518098-08:00","deletedAt":"0001-01-01T00:00:00Z","state":{"status":"Deployed"},"latestManifest":"manifest-1454962670728402229"}`))
}),
}
defer fc.teardown()
d, err := fc.setup().GetDeployment("guestbook.yaml")
if err != nil {
t.Fatal(err)
}
if d.Name != "guestbook.yaml" {
t.Fatalf("expected deployment name 'guestbook.yaml', got '%s'", d.Name)
}
if d.State.Status != common.DeployedStatus {
t.Fatalf("expected deployment status 'Deployed', got '%s'", d.State.Status)
}
}
func TestPostDeployment(t *testing.T) {
cfg := &common.Configuration{
Resources: []*common.Resource{
{
Name: "foo",
Type: "helm:example.com/foo/bar",
Properties: map[string]interface{}{
"port": ":8080",
},
},
},
}
fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
fmt.Fprintln(w, "{}")
}),
}
defer fc.teardown()
if err := fc.setup().PostDeployment("foo", cfg); err != nil {
t.Fatalf("failed to post deployment: %s", err)
}
}

@ -0,0 +1,133 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
fancypath "path"
"path/filepath"
"github.com/ghodss/yaml"
"github.com/kubernetes/helm/pkg/common"
)
// 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
}
// PostChart sends a chart to DM for deploying.
//
// This returns the location for the new chart, typically of the form
// `helm:repo/bucket/name-version.tgz`.
func (c *Client) PostChart(filename, deployname string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
u, err := c.url("/v2/charts")
request, err := http.NewRequest("POST", u, f)
if err != nil {
f.Close()
return "", err
}
// There is an argument to be made for using the legacy x-octet-stream for
// this. But since we control both sides, we should use the standard one.
// Also, gzip (x-compress) is usually treated as a content encoding. In this
// case it probably is not, but it makes more sense to follow the standard,
// even though we don't assume the remote server will strip it off.
request.Header.Add("Content-Type", "application/x-tar")
request.Header.Add("Content-Encoding", "gzip")
request.Header.Add("X-Deployment-Name", deployname)
request.Header.Add("X-Chart-Name", filepath.Base(filename))
request.Header.Set("User-Agent", c.agent())
client := &http.Client{
Timeout: c.HTTPTimeout,
Transport: c.transport(),
}
response, err := client.Do(request)
if err != nil {
return "", err
}
// We only want 201 CREATED. Admittedly, we could accept 200 and 202.
if response.StatusCode != http.StatusCreated {
body, err := ioutil.ReadAll(response.Body)
response.Body.Close()
if err != nil {
return "", err
}
return "", &HTTPError{StatusCode: response.StatusCode, Message: string(body), URL: request.URL}
}
loc := response.Header.Get("Location")
return loc, nil
}
// 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
}
// 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
}
// PostDeployment posts a deployment object to the manager service.
func (c *Client) PostDeployment(name string, cfg *common.Configuration) error {
d, err := yaml.Marshal(cfg)
if err != nil {
return err
}
// This is a stop-gap until we get this API cleaned up.
t := common.Template{
Name: name,
Content: string(d),
}
data, err := json.Marshal(t)
if err != nil {
return err
}
var out struct{}
b := bytes.NewBuffer(data)
return c.CallService("/deployments", "POST", "post deployment", &out, b)
}

@ -0,0 +1,91 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"fmt"
"net/http"
"testing"
"github.com/kubernetes/helm/pkg/common"
)
func TestListDeployments(t *testing.T) {
fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`["guestbook.yaml"]`))
}),
}
defer fc.teardown()
l, err := fc.setup().ListDeployments()
if err != nil {
t.Fatal(err)
}
if len(l) != 1 {
t.Fatal("expected a single deployment")
}
}
func TestGetDeployment(t *testing.T) {
fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"name":"guestbook.yaml","id":0,"createdAt":"2016-02-08T12:17:49.251658308-08:00","deployedAt":"2016-02-08T12:17:49.251658589-08:00","modifiedAt":"2016-02-08T12:17:51.177518098-08:00","deletedAt":"0001-01-01T00:00:00Z","state":{"status":"Deployed"},"latestManifest":"manifest-1454962670728402229"}`))
}),
}
defer fc.teardown()
d, err := fc.setup().GetDeployment("guestbook.yaml")
if err != nil {
t.Fatal(err)
}
if d.Name != "guestbook.yaml" {
t.Fatalf("expected deployment name 'guestbook.yaml', got '%s'", d.Name)
}
if d.State.Status != common.DeployedStatus {
t.Fatalf("expected deployment status 'Deployed', got '%s'", d.State.Status)
}
}
func TestPostDeployment(t *testing.T) {
cfg := &common.Configuration{
Resources: []*common.Resource{
{
Name: "foo",
Type: "helm:example.com/foo/bar",
Properties: map[string]interface{}{
"port": ":8080",
},
},
},
}
fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
fmt.Fprintln(w, "{}")
}),
}
defer fc.teardown()
if err := fc.setup().PostDeployment("foo", cfg); err != nil {
t.Fatalf("failed to post deployment: %s", err)
}
}
Loading…
Cancel
Save