diff --git a/cmd/helm/create.go b/cmd/helm/create.go new file mode 100644 index 000000000..c982f0a40 --- /dev/null +++ b/cmd/helm/create.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + "os/user" + "path/filepath" + + "github.com/deis/tiller/pkg/chart" + "github.com/spf13/cobra" +) + +const createDesc = ` +This command creates a chart directory along with the common files and +directories used in a chart. + +For example, 'helm create foo' will create a directory structure that looks +something like this: + + foo/ + |- Chart.yaml + | + |- values.toml + | + |- templates/ + +'helm create' takes a path for an argument. If directories in the given path +do not exist, Helm will attempt to create them as it goes. If the given +destination exists and there are files in that directory, conflicting files +will be overwritten, but other files will be left alone. +` + +func init() { + RootCommand.AddCommand(createCmd) +} + +var createCmd = &cobra.Command{ + Use: "create [PATH]", + Short: "Create a new chart at the location specified.", + Long: createDesc, + RunE: runCreate, +} + +func runCreate(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("the name of the new chart is required") + } + cname := args[0] + cmd.Printf("Creating %s\n", cname) + + chartname := filepath.Base(cname) + cfile := chart.Chartfile{ + Name: chartname, + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + Maintainers: []*chart.Maintainer{ + {Name: username()}, + }, + } + + if _, err := chart.Create(&cfile, filepath.Dir(cname)); err != nil { + return err + } + + return nil +} + +func username() string { + uname := "Unknown" + u, err := user.Current() + if err == nil { + uname = u.Name + if uname == "" { + uname = u.Username + } + } + return uname +} diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 785dac967..def0dd1da 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -8,6 +8,7 @@ import ( var stdout = os.Stdout +// RootCommand is the top-level command for Helm. var RootCommand = &cobra.Command{ Use: "helm", Short: "The Helm package manager for Kubernetes.", diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 062f6d6b4..7a7f53b31 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" ) -const longDesc = ` +const installDesc = ` This command installs Tiller (the helm server side component) onto your Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) ` @@ -24,7 +24,7 @@ func init() { var initCmd = &cobra.Command{ Use: "init", Short: "Initialize Helm on both client and server.", - Long: longDesc, + Long: installDesc, RunE: RunInit, } diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 6edad5bc8..55a08fe61 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -34,9 +34,8 @@ const ChartfileName string = "Chart.yaml" const ( preTemplates string = "templates/" - preHooks string = "hooks/" - preDocs string = "docs/" - preIcon string = "icon.svg" + preValues string = "values.toml" + preCharts string = "charts/" ) var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") @@ -78,28 +77,14 @@ func (c *Chart) Dir() string { return c.loader.dir() } -// DocsDir returns the directory where the chart's documentation is stored. -func (c *Chart) DocsDir() string { - return filepath.Join(c.loader.dir(), preDocs) -} - -// HooksDir returns the directory where the hooks are stored. -func (c *Chart) HooksDir() string { - return filepath.Join(c.loader.dir(), preHooks) -} - // TemplatesDir returns the directory where the templates are stored. func (c *Chart) TemplatesDir() string { return filepath.Join(c.loader.dir(), preTemplates) } -// Icon returns the path to the icon.svg file. -// -// If an icon is not found in the chart, this will return an error. -func (c *Chart) Icon() (string, error) { - i := filepath.Join(c.Dir(), preIcon) - _, err := os.Stat(i) - return i, err +// ChartsDir returns teh directory where dependency charts are stored. +func (c *Chart) ChartsDir() string { + return filepath.Join(c.loader.dir(), preCharts) } // chartLoader provides load, close, and save implementations for a chart. @@ -174,26 +159,24 @@ func Create(chartfile *Chartfile, dir string) (*Chart, error) { n := fname(chartfile.Name) cdir := filepath.Join(path, n) - if _, err := os.Stat(cdir); err == nil { - return nil, fmt.Errorf("directory already exists: %s", cdir) + if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { + return nil, fmt.Errorf("file %s already exists and is not a directory", cdir) } if err := os.MkdirAll(cdir, 0755); err != nil { return nil, err } - rollback := func() { - // TODO: Should we log failures here? - os.RemoveAll(cdir) + if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil { + return nil, err } - if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil { - rollback() + val := []byte(fmt.Sprintf("# Default Values for %s\n", chartfile.Name)) + if err := ioutil.WriteFile(filepath.Join(cdir, preValues), val, 0644); err != nil { return nil, err } - for _, d := range []string{preHooks, preDocs, preTemplates} { + for _, d := range []string{preTemplates, preCharts} { if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { - rollback() return nil, err } } diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index dd114fe67..e14ffe396 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -19,6 +19,7 @@ package chart import ( "fmt" "io/ioutil" + "os" "path/filepath" "reflect" "testing" @@ -47,6 +48,44 @@ func TestLoadDir(t *testing.T) { } } +func TestCreate(t *testing.T) { + tdir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + cf := &Chartfile{Name: "foo"} + + c, err := Create(cf, tdir) + if err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + + if c.Chartfile().Name != "foo" { + t.Errorf("Expected name to be 'foo', got %q", c.Chartfile().Name) + } + + for _, d := range []string{preTemplates, preCharts} { + if fi, err := os.Stat(filepath.Join(dir, d)); err != nil { + t.Errorf("Expected %s dir: %s", d, err) + } else if !fi.IsDir() { + t.Errorf("Expected %s to be a directory.", d) + } + } + + for _, f := range []string{ChartfileName, preValues} { + if fi, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } else if fi.IsDir() { + t.Errorf("Expected %s to be a fle.", f) + } + } + +} + func TestLoad(t *testing.T) { c, err := Load(testarchive) if err != nil { @@ -102,9 +141,9 @@ func TestChart(t *testing.T) { } dir := c.Dir() - d := c.DocsDir() - if d != filepath.Join(dir, preDocs) { - t.Errorf("Unexpectedly, docs are in %s", d) + d := c.ChartsDir() + if d != filepath.Join(dir, preCharts) { + t.Errorf("Unexpectedly, charts are in %s", d) } d = c.TemplatesDir() diff --git a/pkg/client/install.go b/pkg/client/install.go index 1861727e2..adf34f8d0 100644 --- a/pkg/client/install.go +++ b/pkg/client/install.go @@ -20,7 +20,7 @@ type Installer struct { Tiller map[string]interface{} } -// New Installer creates a new Installer +// NewInstaller creates a new Installer func NewInstaller() *Installer { return &Installer{ Metadata: map[string]interface{}{},