Commit 0bbde9ad authored by Matt Butcher's avatar Matt Butcher

Merge pull request #23 from technosophos/feat/deploy

Implement client side of `helm deploy`
parents a0acb3d5 436b7f03
...@@ -50,8 +50,8 @@ test: test-style ...@@ -50,8 +50,8 @@ test: test-style
$(PATH_WITH_HELM) go test -v -cover $(addprefix ./,$(GO_PACKAGES)) $(PATH_WITH_HELM) go test -v -cover $(addprefix ./,$(GO_PACKAGES))
test-style: test-style:
@if [ $(shell gofmt -e -l -s *.go $(GO_PACKAGES)) ]; then \ @if [ $(shell gofmt -e -l -s $(GO_PACKAGES)) ]; then \
echo "gofmt check failed:"; gofmt -e -l -s *.go $(GO_PACKAGES); exit 1; \ echo "gofmt check failed:"; gofmt -e -d -s $(GO_PACKAGES); exit 1; \
fi fi
@for i in . $(GO_PACKAGES); do \ @for i in . $(GO_PACKAGES); do \
golint $$i; \ golint $$i; \
......
package main package main
import ( import (
"errors" "io/ioutil"
"fmt"
"os"
"regexp"
"strings"
"github.com/aokoli/goutils"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
dep "github.com/deis/helm-dm/deploy" "github.com/kubernetes/deployment-manager/common"
"github.com/deis/helm-dm/format" "gopkg.in/yaml.v2"
"github.com/kubernetes/deployment-manager/chart"
) )
func init() { func init() {
...@@ -29,9 +23,9 @@ func deployCmd() cli.Command { ...@@ -29,9 +23,9 @@ func deployCmd() cli.Command {
Name: "dry-run", Name: "dry-run",
Usage: "Only display the underlying kubectl commands.", Usage: "Only display the underlying kubectl commands.",
}, },
cli.BoolFlag{ cli.StringFlag{
Name: "stdin,i", Name: "config,c",
Usage: "Read a configuration from STDIN.", Usage: "The configuration YAML file for this deployment.",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "name", Name: "name",
...@@ -42,110 +36,57 @@ func deployCmd() cli.Command { ...@@ -42,110 +36,57 @@ func deployCmd() cli.Command {
Name: "properties,p", Name: "properties,p",
Usage: "A comma-separated list of key=value pairs: 'foo=bar,foo2=baz'.", Usage: "A comma-separated list of key=value pairs: 'foo=bar,foo2=baz'.",
}, },
cli.StringFlag{
// FIXME: This is not right. It's sort of a half-baked forward
// port of dm.go.
Name: "repository",
Usage: "The default repository",
Value: "kubernetes/application-dm-templates",
},
}, },
} }
} }
func deploy(c *cli.Context) error { func deploy(c *cli.Context) error {
args := c.Args()
if len(args) < 1 {
format.Err("First argument, filename, is required. Try 'helm deploy --help'")
os.Exit(1)
}
props, err := parseProperties(c.String("properties")) // If there is a configuration file, use it.
if err != nil { cfg := &common.Configuration{}
format.Err("Failed to parse properties: %s", err) if c.String("config") != "" {
os.Exit(1) if err := loadConfig(cfg, c.String("config")); err != nil {
} return err
d := &dep.Deployment{
Name: c.String("Name"),
Properties: props,
Filename: args[0],
Imports: args[1:],
Repository: c.String("repository"),
} }
} else {
if c.Bool("stdin") { cfg.Resources = []*common.Resource{
d.Input = os.Stdin {
Properties: map[string]interface{}{},
},
} }
return doDeploy(d, c)
}
func doDeploy(cfg *dep.Deployment, cxt *cli.Context) error {
if cfg.Filename == "" {
return errors.New("A filename must be specified. For a tar archive, this is the name of the root template in the archive.")
} }
fi, err := os.Stat(cfg.Filename) // If there is a chart specified on the commandline, override the config
if err != nil { // file with it.
return err args := c.Args()
if len(args) > 0 {
cfg.Resources[0].Type = args[0]
} }
if fi.IsDir() { // Override the name if one is passed in.
format.Info("Chart is directory") if name := c.String("name"); len(name) > 0 {
c, err := chart.LoadDir(cfg.Filename) cfg.Resources[0].Name = name
if err != nil {
return err
}
if cfg.Name == "" {
cfg.Name = genName(c.Chartfile().Name)
} }
// TODO: Is it better to generate the file in temp dir like this, or if props, err := parseProperties(c.String("properties")); err != nil {
// just put it in the CWD?
//tdir, err := ioutil.TempDir("", "helm-")
//if err != nil {
//format.Warn("Could not create temporary directory. Using .")
//tdir = "."
//} else {
//defer os.RemoveAll(tdir)
//}
tdir := "."
tfile, err := chart.Save(c, tdir)
if err != nil {
return err return err
} else if len(props) > 0 {
// Coalesce the properties into the first props. We have no way of
// knowing which resource the properties are supposed to be part
// of.
for n, v := range props {
cfg.Resources[0].Properties[n] = v
} }
cfg.Filename = tfile
} else if cfg.Name == "" {
n, _, e := parseTarName(cfg.Filename)
if e != nil {
return e
}
cfg.Name = n
} }
if cxt.Bool("dry-run") { return client(c).PostDeployment(cfg)
format.Info("Prepared deploy %q using file %q", cfg.Name, cfg.Filename)
return nil
}
c := client(cxt)
return c.DeployChart(cfg.Filename, cfg.Name)
}
func genName(pname string) string {
s, _ := goutils.RandomAlphaNumeric(8)
return fmt.Sprintf("%s-%s", pname, s)
} }
func parseTarName(name string) (string, string, error) { // loadConfig loads a file into a common.Configuration.
tnregexp := regexp.MustCompile(chart.TarNameRegex) func loadConfig(c *common.Configuration, filename string) error {
if strings.HasSuffix(name, ".tgz") { data, err := ioutil.ReadFile(filename)
name = strings.TrimSuffix(name, ".tgz") if err != nil {
} return err
v := tnregexp.FindStringSubmatch(name)
if v == nil {
return name, "", fmt.Errorf("invalid name %s", name)
} }
return v[1], v[2], nil return yaml.Unmarshal(data, c)
} }
...@@ -174,14 +174,14 @@ func (c *Client) ListDeployments() ([]string, error) { ...@@ -174,14 +174,14 @@ func (c *Client) ListDeployments() ([]string, error) {
return l, nil return l, nil
} }
// DeployChart sends a chart to DM for deploying. // UploadChart sends a chart to DM for deploying.
func (c *Client) DeployChart(filename, deployname string) error { func (c *Client) PostChart(filename, deployname string) error {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
return err return err
} }
u, err := c.url("/v2/deployments") u, err := c.url("/v2/charts")
request, err := http.NewRequest("POST", u, f) request, err := http.NewRequest("POST", u, f)
if err != nil { if err != nil {
f.Close() f.Close()
...@@ -239,3 +239,7 @@ func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) { ...@@ -239,3 +239,7 @@ func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) {
} }
return deployment, nil return deployment, nil
} }
func (c *Client) PostDeployment(cfg *common.Configuration) error {
return c.CallService("/deployments", "POST", "post deployment", cfg, nil)
}
package dm package dm
import ( import (
"io/ioutil" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"strings" "strings"
"testing" "testing"
...@@ -133,39 +132,28 @@ func TestGetDeployment(t *testing.T) { ...@@ -133,39 +132,28 @@ func TestGetDeployment(t *testing.T) {
} }
} }
func TestDeployChart(t *testing.T) { func TestPostDeployment(t *testing.T) {
testfile := "../testdata/charts/frobnitz-0.0.1.tgz" cfg := &common.Configuration{
testname := "sparkles" []*common.Resource{
{
fi, err := os.Stat(testfile) Name: "foo",
if err != nil { Type: "helm:example.com/foo/bar",
t.Fatalf("could not stat file %s: %s", testfile, err) Properties: map[string]interface{}{
"port": ":8080",
},
},
},
} }
expectedSize := int(fi.Size())
fc := &fakeClient{ fc := &fakeClient{
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body) w.WriteHeader(http.StatusCreated)
if err != nil { fmt.Fprintln(w, "{}")
t.Errorf("Failed to read data off of request: %s", err)
}
if len(data) != expectedSize {
t.Errorf("Expected content length %d, got %d", expectedSize, len(data))
}
if cn := r.Header.Get("x-chart-name"); cn != "frobnitz-0.0.1.tgz" {
t.Errorf("Expected frobnitz-0.0.1.tgz, got %q", cn)
}
if dn := r.Header.Get("x-deployment-name"); dn != "sparkles" {
t.Errorf("Expected sparkles, got %q", dn)
}
w.WriteHeader(201)
}), }),
} }
defer fc.teardown() defer fc.teardown()
if err := fc.setup().DeployChart(testfile, testname); err != nil { if err := fc.setup().PostDeployment(cfg); err != nil {
t.Fatal(err) t.Fatalf("failed to post deployment: %s", 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