feat(helm): support 'helm create --pack=mypack'

This adds support for packs, pre-configured chart patterns that can be
used to quickly create a custom layout for your new chart.
pull/1547/head
Matt Butcher 8 years ago
parent 915769b311
commit 784a339627
No known key found for this signature in database
GPG Key ID: DCD5F5E5EF32C345

@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart"
)
@ -54,8 +55,10 @@ will be overwritten, but other files will be left alone.
`
type createCmd struct {
name string
out io.Writer
home helmpath.Home
name string
out io.Writer
starter string
}
func newCreateCmd(out io.Writer) *cobra.Command {
@ -68,6 +71,7 @@ func newCreateCmd(out io.Writer) *cobra.Command {
Short: "create a new chart with the given name",
Long: createDesc,
RunE: func(cmd *cobra.Command, args []string) error {
cc.home = helmpath.Home(homePath())
if len(args) == 0 {
return errors.New("the name of the new chart is required")
}
@ -76,6 +80,7 @@ func newCreateCmd(out io.Writer) *cobra.Command {
},
}
cmd.Flags().StringVarP(&cc.starter, "starter", "p", "", "the named Helm starter scaffold")
return cmd
}
@ -90,6 +95,12 @@ func (c *createCmd) run() error {
ApiVersion: chartutil.ApiVersionV1,
}
if c.starter != "" {
// Create from the starter
lstarter := filepath.Join(c.home.Starters(), c.starter)
return chartutil.CreateFrom(cfile, filepath.Dir(c.name), lstarter)
}
_, err := chartutil.Create(cfile, filepath.Dir(c.name))
return err
}

@ -19,9 +19,11 @@ package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart"
)
func TestCreateCmd(t *testing.T) {
@ -69,3 +71,93 @@ func TestCreateCmd(t *testing.T) {
t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion)
}
}
func TestCreateStarterCmd(t *testing.T) {
cname := "testchart"
// Make a temp dir
tdir, err := ioutil.TempDir("", "helm-create-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tdir)
thome, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
old := homePath()
helmHome = thome
defer func() {
helmHome = old
os.RemoveAll(thome)
}()
// Create a starter.
starterchart := filepath.Join(thome, "starters")
os.Mkdir(starterchart, 0755)
if dest, err := chartutil.Create(&chart.Metadata{Name: "starterchart"}, starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0755); err != nil {
t.Fatalf("Could not write template: %s", err)
}
// CD into it
pwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(tdir); err != nil {
t.Fatal(err)
}
defer os.Chdir(pwd)
// Run a create
cmd := newCreateCmd(os.Stdout)
cmd.ParseFlags([]string{"--starter", "starterchart"})
if err := cmd.RunE(cmd, []string{cname}); err != nil {
t.Errorf("Failed to run create: %s", err)
return
}
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
c, err := chartutil.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
if c.Metadata.Name != cname {
t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name)
}
if c.Metadata.ApiVersion != chartutil.ApiVersionV1 {
t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion)
}
if l := len(c.Templates); l != 5 {
t.Errorf("Expected 5 templates, got %d", l)
}
found := false
for _, tpl := range c.Templates {
if tpl.Name == "templates/foo.tpl" {
found = true
data := tpl.Data
if string(data) != "test" {
t.Errorf("Expected template 'test', got %q", string(data))
}
}
}
if !found {
t.Error("Did not find foo.tpl")
}
}

@ -241,7 +241,7 @@ func tempHelmHome(t *testing.T) (string, error) {
//
// t is used only for logging.
func ensureTestHome(home helmpath.Home, t *testing.T) error {
configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository()}
configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Starters()}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
if err := os.MkdirAll(p, 0755); err != nil {

@ -53,6 +53,11 @@ func (h Home) CacheIndex(name string) string {
return filepath.Join(string(h), target)
}
// Starters returns the path to the Helm starter packs.
func (h Home) Starters() string {
return filepath.Join(string(h), "starters")
}
// LocalRepository returns the location to the local repo.
//
// The local repo is the one used by 'helm serve'

@ -33,4 +33,5 @@ func TestHelmHome(t *testing.T) {
isEq(t, hh.LocalRepository(), "/r/repository/local")
isEq(t, hh.Cache(), "/r/repository/cache")
isEq(t, hh.CacheIndex("t"), "/r/repository/cache/t-index.yaml")
isEq(t, hh.Starters(), "/r/starters")
}

@ -143,7 +143,7 @@ func (i *initCmd) run() error {
//
// If $HELM_HOME does not exist, this function will create it.
func ensureHome(home helmpath.Home, out io.Writer) error {
configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository()}
configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Starters()}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
fmt.Fprintf(out, "Creating %s \n", p)

@ -543,3 +543,21 @@ commands. However, Helm does not provide tools for uploading charts to
remote repository servers. This is because doing so would add
substantial requirements to an implementing server, and thus raise the
barrier for setting up a repository.
## Chart Starter Packs
The `helm create` command takes an optional `--starter` option that lets you
specify a "starter chart".
Starters are just regular charts, but are located in `$HELM_HOME/starters`.
As a chart developer, you may author charts that are specifically designed
to be used as starters. Such charts should be designed with the following
considerations in mind:
- The `Chart.yaml` will be overwritten by the genertor.
- Users will expect to modify such a chart's contents, so documentation
should indicate how users can do so.
Currently the only way to add a chart to `$HELM_HOME/starters` is to manually
copy it there. In your chart's documentation, you may want to explain that
process.

@ -175,6 +175,17 @@ We truncate at 24 chars because some Kubernetes name fields are limited to this
{{- end -}}
`
// CreateFrom creates a new chart, but scaffolds it from the src chart.
func CreateFrom(chartfile *chart.Metadata, dest string, src string) error {
schart, err := Load(src)
if err != nil {
return fmt.Errorf("could not load %s: %s", src, err)
}
schart.Metadata = chartfile
return SaveDir(schart, dest)
}
// Create creates a new chart in a directory.
//
// Inside of dir, this will create a directory based on the name of

@ -75,3 +75,54 @@ func TestCreate(t *testing.T) {
}
}
func TestCreateFrom(t *testing.T) {
tdir, err := ioutil.TempDir("", "helm-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tdir)
cf := &chart.Metadata{Name: "foo"}
srcdir := "./testdata/mariner"
if err := CreateFrom(cf, tdir, srcdir); err != nil {
t.Fatal(err)
}
dir := filepath.Join(tdir, "foo")
c := filepath.Join(tdir, cf.Name)
mychart, err := LoadDir(c)
if err != nil {
t.Fatalf("Failed to load newly created chart %q: %s", c, err)
}
if mychart.Metadata.Name != "foo" {
t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name)
}
for _, d := range []string{TemplatesDir, ChartsDir} {
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, ValuesfileName, "requirements.yaml"} {
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 file.", f)
}
}
for _, f := range []string{"placeholder.tpl"} {
if fi, err := os.Stat(filepath.Join(dir, TemplatesDir, f)); err != nil {
t.Errorf("Expected %s file: %s", f, err)
} else if fi.IsDir() {
t.Errorf("Expected %s to be a file.", f)
}
}
}

@ -21,6 +21,7 @@ import (
"compress/gzip"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@ -31,6 +32,60 @@ import (
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
// SaveDir saves a chart as files in a directory.
func SaveDir(c *chart.Chart, dest string) error {
// Create the chart directory
outdir := filepath.Join(dest, c.Metadata.Name)
if err := os.Mkdir(outdir, 0755); err != nil {
return err
}
// Save the chart file.
if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil {
return err
}
// Save values.yaml
if c.Values != nil && len(c.Values.Raw) > 0 {
vf := filepath.Join(outdir, ValuesfileName)
if err := ioutil.WriteFile(vf, []byte(c.Values.Raw), 0755); err != nil {
return err
}
}
for _, d := range []string{TemplatesDir, ChartsDir} {
if err := os.MkdirAll(filepath.Join(outdir, d), 0755); err != nil {
return err
}
}
// Save templates
for _, f := range c.Templates {
n := filepath.Join(outdir, f.Name)
if err := ioutil.WriteFile(n, f.Data, 0755); err != nil {
return err
}
}
// Save files
for _, f := range c.Files {
n := filepath.Join(outdir, f.TypeUrl)
if err := ioutil.WriteFile(n, f.Value, 0755); err != nil {
return err
}
}
// Save dependencies
base := filepath.Join(outdir, ChartsDir)
for _, dep := range c.Dependencies {
// Here, we write each dependency as a tar file.
if _, err := Save(dep, base); err != nil {
return err
}
}
return nil
}
// Save creates an archived chart to the given directory.
//
// This takes an existing chart and a destination directory.

@ -65,3 +65,37 @@ func TestSave(t *testing.T) {
t.Fatal("Values data did not match")
}
}
func TestSaveDir(t *testing.T) {
tmp, err := ioutil.TempDir("", "helm-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "ahab",
Version: "1.2.3.4",
},
Values: &chart.Config{
Raw: "ship: Pequod",
},
}
if err := SaveDir(c, tmp); err != nil {
t.Fatalf("Failed to save: %s", err)
}
c2, err := LoadDir(tmp + "/ahab")
if err != nil {
t.Fatal(err)
}
if c2.Metadata.Name != c.Metadata.Name {
t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name)
}
if c2.Values.Raw != c.Values.Raw {
t.Fatal("Values data did not match")
}
}

Loading…
Cancel
Save