mirror of
https://github.com/helm/helm.git
synced 2026-02-22 01:17:47 -05:00
Merge pull request #1547 from technosophos/feat/packs
feat(helm): support 'helm create --starter=mypack'
This commit is contained in:
commit
5517d00a48
11 changed files with 282 additions and 4 deletions
|
|
@ -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…
Reference in a new issue