Commit b9dbc057 authored by Matt Butcher's avatar Matt Butcher

Merge pull request #20 from technosophos/feat/helm-create

feat(helm): implement 'helm create'
parents 88580563 8b36967a
package main
import (
"errors"
"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 # Information about your chart
|
|- values.toml # The default values for your templates
|
|- charts/ # Charts that this chart depends on
|
|- templates/ # The template files
'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",
}
if _, err := chart.Create(&cfile, filepath.Dir(cname)); err != nil {
return err
}
return nil
}
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
var stdout = os.Stdout var stdout = os.Stdout
// RootCommand is the top-level command for Helm.
var RootCommand = &cobra.Command{ var RootCommand = &cobra.Command{
Use: "helm", Use: "helm",
Short: "The Helm package manager for Kubernetes.", Short: "The Helm package manager for Kubernetes.",
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const longDesc = ` const installDesc = `
This command installs Tiller (the helm server side component) onto your This command installs Tiller (the helm server side component) onto your
Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/)
` `
...@@ -24,7 +24,7 @@ func init() { ...@@ -24,7 +24,7 @@ func init() {
var initCmd = &cobra.Command{ var initCmd = &cobra.Command{
Use: "init", Use: "init",
Short: "Initialize Helm on both client and server.", Short: "Initialize Helm on both client and server.",
Long: longDesc, Long: installDesc,
RunE: RunInit, RunE: RunInit,
} }
......
...@@ -34,11 +34,16 @@ const ChartfileName string = "Chart.yaml" ...@@ -34,11 +34,16 @@ const ChartfileName string = "Chart.yaml"
const ( const (
preTemplates string = "templates/" preTemplates string = "templates/"
preHooks string = "hooks/" preValues string = "values.toml"
preDocs string = "docs/" preCharts string = "charts/"
preIcon string = "icon.svg"
) )
const defaultValues = `# Default values for %s.
# This is a TOML-formatted file. https://github.com/toml-lang/toml
# Declare name/value pairs to be passed into your templates.
# name = "value"
`
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
// Chart represents a complete chart. // Chart represents a complete chart.
...@@ -78,28 +83,14 @@ func (c *Chart) Dir() string { ...@@ -78,28 +83,14 @@ func (c *Chart) Dir() string {
return c.loader.dir() 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. // TemplatesDir returns the directory where the templates are stored.
func (c *Chart) TemplatesDir() string { func (c *Chart) TemplatesDir() string {
return filepath.Join(c.loader.dir(), preTemplates) return filepath.Join(c.loader.dir(), preTemplates)
} }
// Icon returns the path to the icon.svg file. // ChartsDir returns teh directory where dependency charts are stored.
// func (c *Chart) ChartsDir() string {
// If an icon is not found in the chart, this will return an error. return filepath.Join(c.loader.dir(), preCharts)
func (c *Chart) Icon() (string, error) {
i := filepath.Join(c.Dir(), preIcon)
_, err := os.Stat(i)
return i, err
} }
// chartLoader provides load, close, and save implementations for a chart. // chartLoader provides load, close, and save implementations for a chart.
...@@ -174,26 +165,24 @@ func Create(chartfile *Chartfile, dir string) (*Chart, error) { ...@@ -174,26 +165,24 @@ func Create(chartfile *Chartfile, dir string) (*Chart, error) {
n := fname(chartfile.Name) n := fname(chartfile.Name)
cdir := filepath.Join(path, n) cdir := filepath.Join(path, n)
if _, err := os.Stat(cdir); err == nil { if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
return nil, fmt.Errorf("directory already exists: %s", cdir) return nil, fmt.Errorf("file %s already exists and is not a directory", cdir)
} }
if err := os.MkdirAll(cdir, 0755); err != nil { if err := os.MkdirAll(cdir, 0755); err != nil {
return nil, err return nil, err
} }
rollback := func() { if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil {
// TODO: Should we log failures here? return nil, err
os.RemoveAll(cdir)
} }
if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil { val := []byte(fmt.Sprintf(defaultValues, chartfile.Name))
rollback() if err := ioutil.WriteFile(filepath.Join(cdir, preValues), val, 0644); err != nil {
return nil, err 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 { if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
rollback()
return nil, err return nil, err
} }
} }
......
...@@ -19,6 +19,7 @@ package chart ...@@ -19,6 +19,7 @@ package chart
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"testing" "testing"
...@@ -47,6 +48,44 @@ func TestLoadDir(t *testing.T) { ...@@ -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) { func TestLoad(t *testing.T) {
c, err := Load(testarchive) c, err := Load(testarchive)
if err != nil { if err != nil {
...@@ -102,9 +141,9 @@ func TestChart(t *testing.T) { ...@@ -102,9 +141,9 @@ func TestChart(t *testing.T) {
} }
dir := c.Dir() dir := c.Dir()
d := c.DocsDir() d := c.ChartsDir()
if d != filepath.Join(dir, preDocs) { if d != filepath.Join(dir, preCharts) {
t.Errorf("Unexpectedly, docs are in %s", d) t.Errorf("Unexpectedly, charts are in %s", d)
} }
d = c.TemplatesDir() d = c.TemplatesDir()
......
...@@ -20,7 +20,7 @@ type Installer struct { ...@@ -20,7 +20,7 @@ type Installer struct {
Tiller map[string]interface{} Tiller map[string]interface{}
} }
// New Installer creates a new Installer // NewInstaller creates a new Installer
func NewInstaller() *Installer { func NewInstaller() *Installer {
return &Installer{ return &Installer{
Metadata: map[string]interface{}{}, Metadata: map[string]interface{}{},
......
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