diff --git a/cmd/helm/deploy.go b/cmd/helm/deploy.go index e436ae46c..bbb2576b9 100644 --- a/cmd/helm/deploy.go +++ b/cmd/helm/deploy.go @@ -1,17 +1,11 @@ package main import ( - "errors" - "fmt" - "os" - "regexp" - "strings" + "io/ioutil" - "github.com/aokoli/goutils" "github.com/codegangsta/cli" - dep "github.com/deis/helm-dm/deploy" - "github.com/deis/helm-dm/format" - "github.com/kubernetes/deployment-manager/chart" + "github.com/kubernetes/deployment-manager/common" + "gopkg.in/yaml.v2" ) func init() { @@ -29,9 +23,9 @@ func deployCmd() cli.Command { Name: "dry-run", Usage: "Only display the underlying kubectl commands.", }, - cli.BoolFlag{ - Name: "stdin,i", - Usage: "Read a configuration from STDIN.", + cli.StringFlag{ + Name: "config,c", + Usage: "The configuration YAML file for this deployment.", }, cli.StringFlag{ Name: "name", @@ -42,110 +36,57 @@ func deployCmd() cli.Command { Name: "properties,p", 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 { - 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 err != nil { - format.Err("Failed to parse properties: %s", err) - os.Exit(1) - } - d := &dep.Deployment{ - Name: c.String("Name"), - Properties: props, - Filename: args[0], - Imports: args[1:], - Repository: c.String("repository"), + // If there is a configuration file, use it. + cfg := &common.Configuration{} + if c.String("config") != "" { + if err := loadConfig(cfg, c.String("config")); err != nil { + return err + } + } else { + cfg.Resources = []*common.Resource{ + { + Properties: map[string]interface{}{}, + }, + } } - if c.Bool("stdin") { - d.Input = os.Stdin + // If there is a chart specified on the commandline, override the config + // file with it. + args := c.Args() + if len(args) > 0 { + cfg.Resources[0].Type = args[0] } - 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.") + // Override the name if one is passed in. + if name := c.String("name"); len(name) > 0 { + cfg.Resources[0].Name = name } - fi, err := os.Stat(cfg.Filename) - if err != nil { + if props, err := parseProperties(c.String("properties")); err != nil { return err - } - - if fi.IsDir() { - format.Info("Chart is directory") - c, err := chart.LoadDir(cfg.Filename) - 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 - // 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 + } 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") { - 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) + return client(c).PostDeployment(cfg) } -func parseTarName(name string) (string, string, error) { - tnregexp := regexp.MustCompile(chart.TarNameRegex) - if strings.HasSuffix(name, ".tgz") { - name = strings.TrimSuffix(name, ".tgz") - } - v := tnregexp.FindStringSubmatch(name) - if v == nil { - return name, "", fmt.Errorf("invalid name %s", name) +// loadConfig loads a file into a common.Configuration. +func loadConfig(c *common.Configuration, filename string) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return err } - return v[1], v[2], nil + return yaml.Unmarshal(data, c) } diff --git a/dm/client.go b/dm/client.go index 15b64518a..0d2606b56 100644 --- a/dm/client.go +++ b/dm/client.go @@ -174,14 +174,14 @@ func (c *Client) ListDeployments() ([]string, error) { return l, nil } -// DeployChart sends a chart to DM for deploying. -func (c *Client) DeployChart(filename, deployname string) error { +// UploadChart sends a chart to DM for deploying. +func (c *Client) PostChart(filename, deployname string) error { f, err := os.Open(filename) if err != nil { return err } - u, err := c.url("/v2/deployments") + u, err := c.url("/v2/charts") request, err := http.NewRequest("POST", u, f) if err != nil { f.Close() @@ -239,3 +239,7 @@ func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) { } return deployment, nil } + +func (c *Client) PostDeployment(cfg *common.Configuration) error { + return c.CallService("/deployments", "POST", "post deployment", cfg, nil) +} diff --git a/dm/client_test.go b/dm/client_test.go index b7b6a816b..6c1473a28 100644 --- a/dm/client_test.go +++ b/dm/client_test.go @@ -1,10 +1,9 @@ package dm import ( - "io/ioutil" + "fmt" "net/http" "net/http/httptest" - "os" "strings" "testing" @@ -133,39 +132,28 @@ func TestGetDeployment(t *testing.T) { } } -func TestDeployChart(t *testing.T) { - testfile := "../testdata/charts/frobnitz-0.0.1.tgz" - testname := "sparkles" - - fi, err := os.Stat(testfile) - if err != nil { - t.Fatalf("could not stat file %s: %s", testfile, err) +func TestPostDeployment(t *testing.T) { + cfg := &common.Configuration{ + []*common.Resource{ + { + Name: "foo", + Type: "helm:example.com/foo/bar", + Properties: map[string]interface{}{ + "port": ":8080", + }, + }, + }, } - expectedSize := int(fi.Size()) fc := &fakeClient{ handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - data, err := ioutil.ReadAll(r.Body) - if err != nil { - 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) + w.WriteHeader(http.StatusCreated) + fmt.Fprintln(w, "{}") }), } defer fc.teardown() - if err := fc.setup().DeployChart(testfile, testname); err != nil { - t.Fatal(err) + if err := fc.setup().PostDeployment(cfg); err != nil { + t.Fatalf("failed to post deployment: %s", err) } }