Merge pull request #4518 from adamreese/dev-v3-ref-chart

ref(*): refactor chart/chartutil
pull/4531/head
Adam Reese 6 years ago committed by GitHub
commit 35e931135c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,8 +24,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
) )
const createDesc = ` const createDesc = `
@ -80,7 +80,7 @@ func (o *createOptions) run(out io.Writer) error {
Description: "A Helm chart for Kubernetes", Description: "A Helm chart for Kubernetes",
Version: "0.1.0", Version: "0.1.0",
AppVersion: "1.0", AppVersion: "1.0",
APIVersion: chartutil.APIVersionv1, APIVersion: chart.APIVersionv1,
} }
if o.starter != "" { if o.starter != "" {

@ -23,8 +23,9 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
) )
func TestCreateCmd(t *testing.T) { func TestCreateCmd(t *testing.T) {
@ -46,15 +47,15 @@ func TestCreateCmd(t *testing.T) {
t.Fatalf("chart is not directory") t.Fatalf("chart is not directory")
} }
c, err := chartutil.LoadDir(cname) c, err := loader.LoadDir(cname)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c.Metadata.Name != cname { if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name) t.Errorf("Expected %q name, got %q", cname, c.Name())
} }
if c.Metadata.APIVersion != chartutil.APIVersionv1 { if c.Metadata.APIVersion != chart.APIVersionv1 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
} }
} }
@ -97,15 +98,15 @@ func TestCreateStarterCmd(t *testing.T) {
t.Fatalf("chart is not directory") t.Fatalf("chart is not directory")
} }
c, err := chartutil.LoadDir(cname) c, err := loader.LoadDir(cname)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c.Metadata.Name != cname { if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name) t.Errorf("Expected %q name, got %q", cname, c.Name())
} }
if c.Metadata.APIVersion != chartutil.APIVersionv1 { if c.Metadata.APIVersion != chart.APIVersionv1 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
} }

@ -26,7 +26,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
) )
const dependencyDesc = ` const dependencyDesc = `
@ -130,27 +131,23 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
} }
func (o *dependencyLisOptions) run(out io.Writer) error { func (o *dependencyLisOptions) run(out io.Writer) error {
c, err := chartutil.Load(o.chartpath) c, err := loader.Load(o.chartpath)
if err != nil { if err != nil {
return err return err
} }
r, err := chartutil.LoadRequirements(c) if c.Requirements == nil {
if err != nil { fmt.Fprintf(out, "WARNING: no requirements at %s/charts\n", o.chartpath)
if err == chartutil.ErrRequirementsNotFound { return nil
fmt.Fprintf(out, "WARNING: no requirements at %s/charts\n", o.chartpath)
return nil
}
return err
} }
o.printRequirements(out, r) o.printRequirements(out, c.Requirements)
fmt.Fprintln(out) fmt.Fprintln(out)
o.printMissing(out, r) o.printMissing(out, c.Requirements)
return nil return nil
} }
func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) string { func (o *dependencyLisOptions) dependencyStatus(dep *chart.Dependency) string {
filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
archives, err := filepath.Glob(filepath.Join(o.chartpath, "charts", filename)) archives, err := filepath.Glob(filepath.Join(o.chartpath, "charts", filename))
if err != nil { if err != nil {
@ -160,11 +157,11 @@ func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) strin
} else if len(archives) == 1 { } else if len(archives) == 1 {
archive := archives[0] archive := archives[0]
if _, err := os.Stat(archive); err == nil { if _, err := os.Stat(archive); err == nil {
c, err := chartutil.Load(archive) c, err := loader.Load(archive)
if err != nil { if err != nil {
return "corrupt" return "corrupt"
} }
if c.Metadata.Name != dep.Name { if c.Name() != dep.Name {
return "misnamed" return "misnamed"
} }
@ -195,12 +192,12 @@ func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) strin
return "mispackaged" return "mispackaged"
} }
c, err := chartutil.Load(folder) c, err := loader.Load(folder)
if err != nil { if err != nil {
return "corrupt" return "corrupt"
} }
if c.Metadata.Name != dep.Name { if c.Name() != dep.Name {
return "misnamed" return "misnamed"
} }
@ -225,7 +222,7 @@ func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) strin
} }
// printRequirements prints all of the requirements in the yaml file. // printRequirements prints all of the requirements in the yaml file.
func (o *dependencyLisOptions) printRequirements(out io.Writer, reqs *chartutil.Requirements) { func (o *dependencyLisOptions) printRequirements(out io.Writer, reqs *chart.Requirements) {
table := uitable.New() table := uitable.New()
table.MaxColWidth = 80 table.MaxColWidth = 80
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
@ -236,7 +233,7 @@ func (o *dependencyLisOptions) printRequirements(out io.Writer, reqs *chartutil.
} }
// printMissing prints warnings about charts that are present on disk, but are not in the requirements. // printMissing prints warnings about charts that are present on disk, but are not in the requirements.
func (o *dependencyLisOptions) printMissing(out io.Writer, reqs *chartutil.Requirements) { func (o *dependencyLisOptions) printMissing(out io.Writer, reqs *chart.Requirements) {
folder := filepath.Join(o.chartpath, "charts/*") folder := filepath.Join(o.chartpath, "charts/*")
files, err := filepath.Glob(folder) files, err := filepath.Glob(folder)
if err != nil { if err != nil {
@ -253,14 +250,14 @@ func (o *dependencyLisOptions) printMissing(out io.Writer, reqs *chartutil.Requi
if !fi.IsDir() && filepath.Ext(f) != ".tgz" { if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
continue continue
} }
c, err := chartutil.Load(f) c, err := loader.Load(f)
if err != nil { if err != nil {
fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f) fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f)
continue continue
} }
found := false found := false
for _, d := range reqs.Dependencies { for _, d := range reqs.Dependencies {
if d.Name == c.Metadata.Name { if d.Name == c.Name() {
found = true found = true
break break
} }

@ -26,9 +26,8 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/provenance" "k8s.io/helm/pkg/provenance"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/repo/repotest" "k8s.io/helm/pkg/repo/repotest"
@ -88,8 +87,8 @@ func TestDependencyUpdateCmd(t *testing.T) {
// Now change the dependencies and update. This verifies that on update, // Now change the dependencies and update. This verifies that on update,
// old dependencies are cleansed and new dependencies are added. // old dependencies are cleansed and new dependencies are added.
reqfile := &chartutil.Requirements{ reqfile := &chart.Requirements{
Dependencies: []*chartutil.Dependency{ Dependencies: []*chart.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()}, {Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
{Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()}, {Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()},
}, },
@ -170,7 +169,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
out := bytes.NewBuffer(nil) out := bytes.NewBuffer(nil)
o := &dependencyUpdateOptions{} o := &dependencyUpdateOptions{}
o.helmhome = helmpath.Home(hh) o.helmhome = hh
o.chartpath = hh.Path(chartname) o.chartpath = hh.Path(chartname)
if err := o.run(out); err != nil { if err := o.run(out); err != nil {
@ -223,8 +222,8 @@ func createTestingChart(dest, name, baseURL string) error {
if err != nil { if err != nil {
return err return err
} }
req := &chartutil.Requirements{ req := &chart.Requirements{
Dependencies: []*chartutil.Dependency{ Dependencies: []*chart.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: baseURL}, {Name: "reqtest", Version: "0.1.0", Repository: baseURL},
{Name: "compressedchart", Version: "0.1.0", Repository: baseURL}, {Name: "compressedchart", Version: "0.1.0", Repository: baseURL},
}, },
@ -232,7 +231,7 @@ func createTestingChart(dest, name, baseURL string) error {
return writeRequirements(dir, req) return writeRequirements(dir, req)
} }
func writeRequirements(dir string, req *chartutil.Requirements) error { func writeRequirements(dir string, req *chart.Requirements) error {
data, err := yaml.Marshal(req) data, err := yaml.Marshal(req)
if err != nil { if err != nil {
return err return err

@ -27,7 +27,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
) )
@ -167,5 +167,5 @@ func formatChartname(c *chart.Chart) string {
// know how: https://github.com/kubernetes/helm/issues/1347 // know how: https://github.com/kubernetes/helm/issues/1347
return "MISSING" return "MISSING"
} }
return fmt.Sprintf("%s-%s", c.Metadata.Name, c.Metadata.Version) return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version)
} }

@ -25,8 +25,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart/loader"
) )
const inspectDesc = ` const inspectDesc = `
@ -146,7 +146,7 @@ func newInspectCmd(out io.Writer) *cobra.Command {
} }
func (i *inspectOptions) run(out io.Writer) error { func (i *inspectOptions) run(out io.Writer) error {
chrt, err := chartutil.Load(i.chartpath) chrt, err := loader.Load(i.chartpath)
if err != nil { if err != nil {
return err return err
} }

@ -28,10 +28,10 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
) )
@ -176,12 +176,12 @@ func (o *installOptions) run(out io.Writer) error {
} }
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts
chartRequested, err := chartutil.Load(o.chartPath) chartRequested, err := loader.Load(o.chartPath)
if err != nil { if err != nil {
return err return err
} }
if req, err := chartutil.LoadRequirements(chartRequested); err == nil { if req := chartRequested.Requirements; req != nil {
// If checkDependencies returns an error, we have unfulfilled dependencies. // If checkDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition: // As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/kubernetes/helm/issues/2209 // https://github.com/kubernetes/helm/issues/2209
@ -203,8 +203,6 @@ func (o *installOptions) run(out io.Writer) error {
} }
} }
} else if err != chartutil.ErrRequirementsNotFound {
return errors.Wrap(err, "cannot load requirements")
} }
rel, err := o.client.InstallReleaseFromChart( rel, err := o.client.InstallReleaseFromChart(
@ -272,7 +270,6 @@ func (o *installOptions) printRelease(out io.Writer, rel *release.Release) {
if rel == nil { if rel == nil {
return return
} }
// TODO: Switch to text/template like everything else.
fmt.Fprintf(out, "NAME: %s\n", rel.Name) fmt.Fprintf(out, "NAME: %s\n", rel.Name)
if settings.Debug { if settings.Debug {
printRelease(out, rel) printRelease(out, rel)
@ -286,27 +283,20 @@ func generateName(nameTemplate string) (string, error) {
} }
var b bytes.Buffer var b bytes.Buffer
err = t.Execute(&b, nil) err = t.Execute(&b, nil)
if err != nil { return b.String(), err
return "", err
}
return b.String(), nil
} }
func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { func checkDependencies(ch *chart.Chart, reqs *chart.Requirements) error {
missing := []string{} var missing []string
deps := ch.Dependencies OUTER:
for _, r := range reqs.Dependencies { for _, r := range reqs.Dependencies {
found := false for _, d := range ch.Dependencies() {
for _, d := range deps { if d.Name() == r.Name {
if d.Metadata.Name == r.Name { continue OUTER
found = true
break
} }
} }
if !found { missing = append(missing, r.Name)
missing = append(missing, r.Name)
}
} }
if len(missing) > 0 { if len(missing) > 0 {

@ -30,10 +30,11 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/provenance" "k8s.io/helm/pkg/provenance"
) )
@ -129,7 +130,7 @@ func (o *packageOptions) run(out io.Writer) error {
} }
} }
ch, err := chartutil.LoadDir(path) ch, err := loader.LoadDir(path)
if err != nil { if err != nil {
return err return err
} }
@ -161,18 +162,14 @@ func (o *packageOptions) run(out io.Writer) error {
debug("Setting appVersion to %s", o.appVersion) debug("Setting appVersion to %s", o.appVersion)
} }
if filepath.Base(path) != ch.Metadata.Name { if filepath.Base(path) != ch.Name() {
return errors.Errorf("directory name (%s) and Chart.yaml name (%s) must match", filepath.Base(path), ch.Metadata.Name) return errors.Errorf("directory name (%s) and Chart.yaml name (%s) must match", filepath.Base(path), ch.Name())
} }
if reqs, err := chartutil.LoadRequirements(ch); err == nil { if reqs := ch.Requirements; reqs != nil {
if err := checkDependencies(ch, reqs); err != nil { if err := checkDependencies(ch, reqs); err != nil {
return err return err
} }
} else {
if err != chartutil.ErrRequirementsNotFound {
return err
}
} }
var dest string var dest string

@ -26,8 +26,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
@ -206,7 +207,7 @@ func TestSetAppVersion(t *testing.T) {
tmp := testTempDir(t) tmp := testTempDir(t)
hh := testHelmHome(t) hh := testHelmHome(t)
settings.Home = helmpath.Home(hh) settings.Home = hh
c := newPackageCmd(&bytes.Buffer{}) c := newPackageCmd(&bytes.Buffer{})
flags := map[string]string{ flags := map[string]string{
@ -224,7 +225,7 @@ func TestSetAppVersion(t *testing.T) {
} else if fi.Size() == 0 { } else if fi.Size() == 0 {
t.Errorf("file %q has zero bytes.", chartPath) t.Errorf("file %q has zero bytes.", chartPath)
} }
ch, err := chartutil.Load(chartPath) ch, err := loader.Load(chartPath)
if err != nil { if err != nil {
t.Errorf("unexpected error loading packaged chart: %v", err) t.Errorf("unexpected error loading packaged chart: %v", err)
} }
@ -332,7 +333,7 @@ func createValuesFile(t *testing.T, data string) string {
func getChartValues(chartPath string) (chartutil.Values, error) { func getChartValues(chartPath string) (chartutil.Values, error) {
chart, err := chartutil.Load(chartPath) chart, err := loader.Load(chartPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -45,7 +45,7 @@ func TestUpdateCmd(t *testing.T) {
} }
o := &repoUpdateOptions{ o := &repoUpdateOptions{
update: updater, update: updater,
home: helmpath.Home(hh), home: hh,
} }
if err := o.run(out); err != nil { if err := o.run(out); err != nil {
t.Fatal(err) t.Fatal(err)

@ -20,7 +20,7 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )

@ -31,12 +31,12 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/engine"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
util "k8s.io/helm/pkg/releaseutil" util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/tiller" "k8s.io/helm/pkg/tiller"
tversion "k8s.io/helm/pkg/version"
) )
const defaultDirectoryPermission = 0755 const defaultDirectoryPermission = 0755
@ -152,17 +152,15 @@ func (o *templateOptions) run(out io.Writer) error {
} }
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts
c, err := chartutil.Load(o.chartPath) c, err := loader.Load(o.chartPath)
if err != nil { if err != nil {
return err return err
} }
if req, err := chartutil.LoadRequirements(c); err == nil { if req := c.Requirements; req != nil {
if err := checkDependencies(c, req); err != nil { if err := checkDependencies(c, req); err != nil {
return err return err
} }
} else if err != chartutil.ErrRequirementsNotFound {
return errors.Wrap(err, "cannot load requirements")
} }
options := chartutil.ReleaseOptions{ options := chartutil.ReleaseOptions{
Name: o.releaseName, Name: o.releaseName,
@ -178,22 +176,18 @@ func (o *templateOptions) run(out io.Writer) error {
// Set up engine. // Set up engine.
renderer := engine.New() renderer := engine.New()
caps := &chartutil.Capabilities{
APIVersions: chartutil.DefaultVersionSet,
KubeVersion: chartutil.DefaultKubeVersion,
HelmVersion: tversion.GetBuildInfo(),
}
// kubernetes version // kubernetes version
kv, err := semver.NewVersion(o.kubeVersion) kv, err := semver.NewVersion(o.kubeVersion)
if err != nil { if err != nil {
return errors.Wrap(err, "could not parse a kubernetes version") return errors.Wrap(err, "could not parse a kubernetes version")
} }
caps := chartutil.DefaultCapabilities
caps.KubeVersion.Major = fmt.Sprint(kv.Major()) caps.KubeVersion.Major = fmt.Sprint(kv.Major())
caps.KubeVersion.Minor = fmt.Sprint(kv.Minor()) caps.KubeVersion.Minor = fmt.Sprint(kv.Minor())
caps.KubeVersion.GitVersion = fmt.Sprintf("v%d.%d.0", kv.Major(), kv.Minor()) caps.KubeVersion.GitVersion = fmt.Sprintf("v%d.%d.0", kv.Major(), kv.Minor())
vals, err := chartutil.ToRenderValuesCaps(c, config, options, caps) vals, err := chartutil.ToRenderValues(c, config, options, caps)
if err != nil { if err != nil {
return err return err
} }

@ -1 +1 @@
Error: cannot load requirements: error converting YAML to JSON: yaml: line 2: did not find expected '-' indicator Error: cannot load requirements.yaml: error converting YAML to JSON: yaml: line 2: did not find expected '-' indicator

@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
) )
@ -150,17 +150,15 @@ func (o *upgradeOptions) run(out io.Writer) error {
} }
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts
if ch, err := chartutil.Load(chartPath); err == nil { ch, err := loader.Load(chartPath)
if req, err := chartutil.LoadRequirements(ch); err == nil { if err != nil {
if err := checkDependencies(ch, req); err != nil {
return err
}
} else if err != chartutil.ErrRequirementsNotFound {
return errors.Wrap(err, "cannot load requirements")
}
} else {
return err return err
} }
if req := ch.Requirements; req != nil {
if err := checkDependencies(ch, req); err != nil {
return err
}
}
resp, err := o.client.UpdateRelease( resp, err := o.client.UpdateRelease(
o.release, o.release,

@ -19,8 +19,9 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
) )
@ -36,7 +37,7 @@ func TestUpgradeCmd(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error creating chart for upgrade: %v", err) t.Fatalf("Error creating chart for upgrade: %v", err)
} }
ch, err := chartutil.Load(chartPath) ch, err := loader.Load(chartPath)
if err != nil { if err != nil {
t.Fatalf("Error loading chart: %v", err) t.Fatalf("Error loading chart: %v", err)
} }
@ -56,7 +57,7 @@ func TestUpgradeCmd(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error creating chart: %v", err) t.Fatalf("Error creating chart: %v", err)
} }
ch, err = chartutil.Load(chartPath) ch, err = loader.Load(chartPath)
if err != nil { if err != nil {
t.Fatalf("Error loading updated chart: %v", err) t.Fatalf("Error loading updated chart: %v", err)
} }
@ -73,7 +74,7 @@ func TestUpgradeCmd(t *testing.T) {
t.Fatalf("Error creating chart: %v", err) t.Fatalf("Error creating chart: %v", err)
} }
var ch2 *chart.Chart var ch2 *chart.Chart
ch2, err = chartutil.Load(chartPath) ch2, err = loader.Load(chartPath)
if err != nil { if err != nil {
t.Fatalf("Error loading updated chart: %v", err) t.Fatalf("Error loading updated chart: %v", err)
} }

@ -0,0 +1,7 @@
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0
home: https://github.com/kubernetes/helm
sources:
- https://github.com/kubernetes/helm
appVersion: 3.3

@ -0,0 +1,11 @@
# Alpine: A simple Helm chart
Run a single pod of Alpine Linux.
The `templates/` directory contains a very simple pod resource with a
couple of parameters.
The `values.yaml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install docs/examples/alpine`.

@ -0,0 +1,16 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "alpine.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "alpine.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

@ -0,0 +1,23 @@
apiVersion: v1
kind: Pod
metadata:
name: {{ template "alpine.fullname" . }}
labels:
# The "heritage" label is used to track which tool deployed a given chart.
# It is useful for admins who want to see what releases a particular tool
# is responsible for.
heritage: {{ .Release.Service }}
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{ .Release.Name }}
# This makes it easy to audit chart usage.
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "alpine.name" . }}
spec:
# This shows how to use a simple value. This will look for a passed-in value called restartPolicy.
restartPolicy: {{ .Values.restartPolicy }}
containers:
- name: waiter
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/sleep", "9000"]

@ -0,0 +1,6 @@
image:
repository: alpine
tag: 3.3
pullPolicy: IfNotPresent
restartPolicy: Never

@ -0,0 +1 @@
Sample notes for {{ .Chart.Name }}

@ -0,0 +1,95 @@
/*
Copyright 2018 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chart
// Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies).
type Chart struct {
// Metadata is the contents of the Chartfile.
Metadata *Metadata
// Requirements is the contents of requirements.yaml.
Requirements *Requirements
// RequirementsLock is the contents of requirements.lock.
RequirementsLock *RequirementsLock
// Templates for this chart.
Templates []*File
// Values are default config for this template.
Values []byte
// Files are miscellaneous files in a chart archive,
// e.g. README, LICENSE, etc.
Files []*File
parent *Chart
dependencies []*Chart
}
// SetDependencies replaces the chart dependencies.
func (ch *Chart) SetDependencies(charts ...*Chart) {
ch.dependencies = nil
ch.AddDependency(charts...)
}
// Name returns the name of the chart.
func (ch *Chart) Name() string {
if ch.Metadata == nil {
return ""
}
return ch.Metadata.Name
}
// AddDependency determines if the chart is a subchart.
func (ch *Chart) AddDependency(charts ...*Chart) {
for i, x := range charts {
charts[i].parent = ch
ch.dependencies = append(ch.dependencies, x)
}
}
// Root finds the root chart.
func (ch *Chart) Root() *Chart {
if ch.IsRoot() {
return ch
}
return ch.Parent().Root()
}
// Dependencies are the charts that this chart depends on.
func (ch *Chart) Dependencies() []*Chart { return ch.dependencies }
// IsRoot determines if the chart is the root chart.
func (ch *Chart) IsRoot() bool { return ch.parent == nil }
// Parent returns a subchart's parent chart.
func (ch *Chart) Parent() *Chart { return ch.parent }
// Parent sets a subchart's parent chart.
func (ch *Chart) SetParent(chart *Chart) { ch.parent = chart }
// ChartPath returns the full path to this chart in dot notation.
func (ch *Chart) ChartPath() string {
if !ch.IsRoot() {
return ch.Parent().ChartPath() + "." + ch.Name()
}
return ch.Name()
}
// ChartFullPath returns the full path to this chart.
func (ch *Chart) ChartFullPath() string {
if !ch.IsRoot() {
return ch.Parent().ChartFullPath() + "/charts/" + ch.Name()
}
return ch.Name()
}

@ -1,11 +1,10 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright 2018 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -14,12 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package chart
import "strings"
// Transform performs a string replacement of the specified source for // APIVersionv1 is the API version number for version 1.
// a given key with the replacement string const APIVersionv1 = "v1"
func Transform(src, key, replacement string) []byte {
return []byte(strings.Replace(src, key, replacement, -1))
}

@ -21,7 +21,7 @@ package chart
// base directory. // base directory.
type File struct { type File struct {
// Name is the path-like name of the template. // Name is the path-like name of the template.
Name string `json:"name,omitempty"` Name string
// Data is the template as byte data. // Data is the template as byte data.
Data []byte `json:"data,omitempty"` Data []byte
} }

@ -0,0 +1,110 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
"os"
"strings"
"github.com/pkg/errors"
"k8s.io/helm/pkg/chart"
)
type FileLoader string
func (l FileLoader) Load() (*chart.Chart, error) {
return LoadFile(string(l))
}
// LoadFile loads from an archive file.
func LoadFile(name string) (*chart.Chart, error) {
if fi, err := os.Stat(name); err != nil {
return nil, err
} else if fi.IsDir() {
return nil, errors.New("cannot load a directory")
}
raw, err := os.Open(name)
if err != nil {
return nil, err
}
defer raw.Close()
return LoadArchive(raw)
}
// LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (*chart.Chart, error) {
unzipped, err := gzip.NewReader(in)
if err != nil {
return &chart.Chart{}, err
}
defer unzipped.Close()
files := []*BufferedFile{}
tr := tar.NewReader(unzipped)
for {
b := bytes.NewBuffer(nil)
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return &chart.Chart{}, err
}
if hd.FileInfo().IsDir() {
// Use this instead of hd.Typeflag because we don't have to do any
// inference chasing.
continue
}
// Archive could contain \ if generated on Windows
delimiter := "/"
if strings.ContainsRune(hd.Name, '\\') {
delimiter = "\\"
}
parts := strings.Split(hd.Name, delimiter)
n := strings.Join(parts[1:], delimiter)
// Normalize the path to the / delimiter
n = strings.Replace(n, delimiter, "/", -1)
if parts[0] == "Chart.yaml" {
return nil, errors.New("chart yaml not in base directory")
}
if _, err := io.Copy(b, tr); err != nil {
return &chart.Chart{}, err
}
files = append(files, &BufferedFile{Name: n, Data: b.Bytes()})
b.Reset()
}
if len(files) == 0 {
return nil, errors.New("no files in chart archive")
}
return LoadFiles(files)
}

@ -0,0 +1,105 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/ignore"
"k8s.io/helm/pkg/sympath"
)
type DirLoader string
func (l DirLoader) Load() (*chart.Chart, error) {
return LoadDir(string(l))
}
// LoadDir loads from a directory.
//
// This loads charts only from directories.
func LoadDir(dir string) (*chart.Chart, error) {
topdir, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
// Just used for errors.
c := &chart.Chart{}
rules := ignore.Empty()
ifile := filepath.Join(topdir, ignore.HelmIgnore)
if _, err := os.Stat(ifile); err == nil {
r, err := ignore.ParseFile(ifile)
if err != nil {
return c, err
}
rules = r
}
rules.AddDefaults()
files := []*BufferedFile{}
topdir += string(filepath.Separator)
walk := func(name string, fi os.FileInfo, err error) error {
n := strings.TrimPrefix(name, topdir)
if n == "" {
// No need to process top level. Avoid bug with helmignore .* matching
// empty names. See issue 1779.
return nil
}
// Normalize to / since it will also work on Windows
n = filepath.ToSlash(n)
if err != nil {
return err
}
if fi.IsDir() {
// Directory-based ignore rules should involve skipping the entire
// contents of that directory.
if rules.Ignore(n, fi) {
return filepath.SkipDir
}
return nil
}
// If a .helmignore file matches, skip this file.
if rules.Ignore(n, fi) {
return nil
}
data, err := ioutil.ReadFile(name)
if err != nil {
return errors.Wrapf(err, "error reading %s", n)
}
files = append(files, &BufferedFile{Name: n, Data: data})
return nil
}
if err = sympath.Walk(topdir, walk); err != nil {
return c, err
}
return LoadFiles(files)
}

@ -0,0 +1,151 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"bytes"
"os"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"k8s.io/helm/pkg/chart"
)
type ChartLoader interface {
Load() (*chart.Chart, error)
}
func Loader(name string) (ChartLoader, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if fi.IsDir() {
return DirLoader(name), nil
}
return FileLoader(name), nil
}
// Load takes a string name, tries to resolve it to a file or directory, and then loads it.
//
// This is the preferred way to load a chart. It will discover the chart encoding
// and hand off to the appropriate chart reader.
//
// If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive.
func Load(name string) (*chart.Chart, error) {
l, err := Loader(name)
if err != nil {
return nil, err
}
return l.Load()
}
// BufferedFile represents an archive file buffered for later processing.
type BufferedFile struct {
Name string
Data []byte
}
// LoadFiles loads from in-memory files.
func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
c := new(chart.Chart)
subcharts := make(map[string][]*BufferedFile)
for _, f := range files {
switch {
case f.Name == "Chart.yaml":
c.Metadata = new(chart.Metadata)
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
return c, errors.Wrap(err, "cannot load Chart.yaml")
}
case f.Name == "requirements.yaml":
c.Requirements = new(chart.Requirements)
if err := yaml.Unmarshal(f.Data, c.Requirements); err != nil {
return c, errors.Wrap(err, "cannot load requirements.yaml")
}
case f.Name == "requirements.lock":
c.RequirementsLock = new(chart.RequirementsLock)
if err := yaml.Unmarshal(f.Data, &c.RequirementsLock); err != nil {
return c, errors.Wrap(err, "cannot load requirements.lock")
}
case f.Name == "values.yaml":
c.Values = f.Data
case strings.HasPrefix(f.Name, "templates/"):
c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data})
case strings.HasPrefix(f.Name, "charts/"):
if filepath.Ext(f.Name) == ".prov" {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
continue
}
fname := strings.TrimPrefix(f.Name, "charts/")
cname := strings.SplitN(fname, "/", 2)[0]
subcharts[cname] = append(subcharts[cname], &BufferedFile{Name: fname, Data: f.Data})
default:
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
}
}
// Ensure that we got a Chart.yaml file
if c.Metadata == nil {
return c, errors.New("chart metadata (Chart.yaml) missing")
}
if c.Name() == "" {
return c, errors.New("invalid chart (Chart.yaml): name must not be empty")
}
for n, files := range subcharts {
var sc *chart.Chart
var err error
switch {
case strings.IndexAny(n, "_.") == 0:
continue
case filepath.Ext(n) == ".tgz":
file := files[0]
if file.Name != n {
return c, errors.Errorf("error unpacking tar in %s: expected %s, got %s", c.Name(), n, file.Name)
}
// Untar the chart and add to c.Dependencies
sc, err = LoadArchive(bytes.NewBuffer(file.Data))
default:
// We have to trim the prefix off of every file, and ignore any file
// that is in charts/, but isn't actually a chart.
buff := make([]*BufferedFile, 0, len(files))
for _, f := range files {
parts := strings.SplitN(f.Name, "/", 2)
if len(parts) < 2 {
continue
}
f.Name = parts[1]
buff = append(buff, f)
}
sc, err = LoadFiles(buff)
}
if err != nil {
return c, errors.Wrapf(err, "error unpacking %s in %s", n, c.Name())
}
c.AddDependency(sc)
}
return c, nil
}

@ -14,27 +14,35 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chartutil package loader
import ( import (
"path"
"testing" "testing"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
) )
func TestLoadDir(t *testing.T) { func TestLoadDir(t *testing.T) {
c, err := Load("testdata/frobnitz") l, err := Loader("testdata/frobnitz")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load()
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
verifyFrobnitz(t, c) verifyFrobnitz(t, c)
verifyChart(t, c) verifyChart(t, c)
verifyRequirements(t, c) verifyRequirements(t, c)
verifyRequirementsLock(t, c)
} }
func TestLoadFile(t *testing.T) { func TestLoadFile(t *testing.T) {
c, err := Load("testdata/frobnitz-1.2.3.tgz") l, err := Loader("testdata/frobnitz-1.2.3.tgz")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load()
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -46,7 +54,7 @@ func TestLoadFile(t *testing.T) {
func TestLoadFiles(t *testing.T) { func TestLoadFiles(t *testing.T) {
goodFiles := []*BufferedFile{ goodFiles := []*BufferedFile{
{ {
Name: ChartfileName, Name: "Chart.yaml",
Data: []byte(`apiVersion: v1 Data: []byte(`apiVersion: v1
name: frobnitz name: frobnitz
description: This is a frobnitz. description: This is a frobnitz.
@ -67,16 +75,16 @@ icon: https://example.com/64x64.png
`), `),
}, },
{ {
Name: ValuesfileName, Name: "values.yaml",
Data: []byte(defaultValues), Data: []byte("some values"),
}, },
{ {
Name: path.Join("templates", DeploymentName), Name: "templates/deployment.yaml",
Data: []byte(defaultDeployment), Data: []byte("some deployment"),
}, },
{ {
Name: path.Join("templates", ServiceName), Name: "templates/service.yaml",
Data: []byte(defaultService), Data: []byte("some service"),
}, },
} }
@ -85,11 +93,11 @@ icon: https://example.com/64x64.png
t.Errorf("Expected good files to be loaded, got %v", err) t.Errorf("Expected good files to be loaded, got %v", err)
} }
if c.Metadata.Name != "frobnitz" { if c.Name() != "frobnitz" {
t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Metadata.Name) t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name())
} }
if string(c.Values) != defaultValues { if string(c.Values) != "some values" {
t.Error("Expected chart values to be populated with default values") t.Error("Expected chart values to be populated with default values")
} }
@ -119,15 +127,16 @@ func TestLoadFileBackslash(t *testing.T) {
} }
func verifyChart(t *testing.T, c *chart.Chart) { func verifyChart(t *testing.T, c *chart.Chart) {
if c.Metadata.Name == "" { t.Helper()
if c.Name() == "" {
t.Fatalf("No chart metadata found on %v", c) t.Fatalf("No chart metadata found on %v", c)
} }
t.Logf("Verifying chart %s", c.Metadata.Name) t.Logf("Verifying chart %s", c.Name())
if len(c.Templates) != 1 { if len(c.Templates) != 1 {
t.Errorf("Expected 1 template, got %d", len(c.Templates)) t.Errorf("Expected 1 template, got %d", len(c.Templates))
} }
numfiles := 8 numfiles := 6
if len(c.Files) != numfiles { if len(c.Files) != numfiles {
t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files))
for _, n := range c.Files { for _, n := range c.Files {
@ -135,10 +144,10 @@ func verifyChart(t *testing.T, c *chart.Chart) {
} }
} }
if len(c.Dependencies) != 2 { if len(c.Dependencies()) != 2 {
t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies), c.Dependencies) t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies())
for _, d := range c.Dependencies { for _, d := range c.Dependencies() {
t.Logf("\tSubchart: %s\n", d.Metadata.Name) t.Logf("\tSubchart: %s\n", d.Name())
} }
} }
@ -151,35 +160,31 @@ func verifyChart(t *testing.T, c *chart.Chart) {
}, },
} }
for _, dep := range c.Dependencies { for _, dep := range c.Dependencies() {
if dep.Metadata == nil { if dep.Metadata == nil {
t.Fatalf("expected metadata on dependency: %v", dep) t.Fatalf("expected metadata on dependency: %v", dep)
} }
exp, ok := expect[dep.Metadata.Name] exp, ok := expect[dep.Name()]
if !ok { if !ok {
t.Fatalf("Unknown dependency %s", dep.Metadata.Name) t.Fatalf("Unknown dependency %s", dep.Name())
} }
if exp["version"] != dep.Metadata.Version { if exp["version"] != dep.Metadata.Version {
t.Errorf("Expected %s version %s, got %s", dep.Metadata.Name, exp["version"], dep.Metadata.Version) t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
} }
} }
} }
func verifyRequirements(t *testing.T, c *chart.Chart) { func verifyRequirements(t *testing.T, c *chart.Chart) {
r, err := LoadRequirements(c) if len(c.Requirements.Dependencies) != 2 {
if err != nil { t.Errorf("Expected 2 requirements, got %d", len(c.Requirements.Dependencies))
t.Fatal(err)
}
if len(r.Dependencies) != 2 {
t.Errorf("Expected 2 requirements, got %d", len(r.Dependencies))
} }
tests := []*Dependency{ tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
} }
for i, tt := range tests { for i, tt := range tests {
d := r.Dependencies[i] d := c.Requirements.Dependencies[i]
if d.Name != tt.Name { if d.Name != tt.Name {
t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name)
} }
@ -191,20 +196,17 @@ func verifyRequirements(t *testing.T, c *chart.Chart) {
} }
} }
} }
func verifyRequirementsLock(t *testing.T, c *chart.Chart) { func verifyRequirementsLock(t *testing.T, c *chart.Chart) {
r, err := LoadRequirementsLock(c) if len(c.Requirements.Dependencies) != 2 {
if err != nil { t.Errorf("Expected 2 requirements, got %d", len(c.Requirements.Dependencies))
t.Fatal(err)
}
if len(r.Dependencies) != 2 {
t.Errorf("Expected 2 requirements, got %d", len(r.Dependencies))
} }
tests := []*Dependency{ tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
} }
for i, tt := range tests { for i, tt := range tests {
d := r.Dependencies[i] d := c.Requirements.Dependencies[i]
if d.Name != tt.Name { if d.Name != tt.Name {
t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name)
} }
@ -223,17 +225,55 @@ func verifyFrobnitz(t *testing.T, c *chart.Chart) {
func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
verifyChartfile(t, c.Metadata, name) if c.Metadata == nil {
t.Fatal("Metadata is nil")
}
if c.Name() != name {
t.Errorf("Expected %s, got %s", name, c.Name())
}
if len(c.Templates) != 1 { if len(c.Templates) != 1 {
t.Fatalf("Expected 1 template, got %d", len(c.Templates)) t.Fatalf("Expected 1 template, got %d", len(c.Templates))
} }
if c.Templates[0].Name != "templates/template.tpl" { if c.Templates[0].Name != "templates/template.tpl" {
t.Errorf("Unexpected template: %s", c.Templates[0].Name) t.Errorf("Unexpected template: %s", c.Templates[0].Name)
} }
if len(c.Templates[0].Data) == 0 { if len(c.Templates[0].Data) == 0 {
t.Error("No template data.") t.Error("No template data.")
} }
if len(c.Files) != 6 {
t.Fatalf("Expected 6 Files, got %d", len(c.Files))
}
if len(c.Dependencies()) != 2 {
t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies()))
}
if len(c.Requirements.Dependencies) != 2 {
t.Fatalf("Expected 2 Requirements.Dependency, got %d", len(c.Requirements.Dependencies))
}
if len(c.RequirementsLock.Dependencies) != 2 {
t.Fatalf("Expected 2 RequirementsLock.Dependency, got %d", len(c.RequirementsLock.Dependencies))
}
for _, dep := range c.Dependencies() {
switch dep.Name() {
case "mariner":
case "alpine":
if len(dep.Templates) != 1 {
t.Fatalf("Expected 1 template, got %d", len(dep.Templates))
}
if dep.Templates[0].Name != "templates/alpine-pod.yaml" {
t.Errorf("Unexpected template: %s", dep.Templates[0].Name)
}
if len(dep.Templates[0].Data) == 0 {
t.Error("No template data.")
}
if len(dep.Files) != 1 {
t.Fatalf("Expected 1 Files, got %d", len(dep.Files))
}
if len(dep.Dependencies()) != 2 {
t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies()))
}
default:
t.Errorf("Unexpected dependeny %s", dep.Name())
}
}
} }

Binary file not shown.

@ -0,0 +1,20 @@
apiVersion: v1
name: frobnitz
description: This is a frobnitz.
version: "1.2.3"
keywords:
- frobnitz
- sprocket
- dodad
maintainers:
- name: The Helm Team
email: helm@example.com
- name: Someone Else
email: nobody@example.com
sources:
- https://example.com/foo/bar
home: http://example.com
icon: https://example.com/64x64.png
annotations:
extrakey: extravalue
anotherkey: anothervalue

@ -0,0 +1 @@
This is an install document. The client may display this.

@ -0,0 +1 @@
LICENSE placeholder.

@ -0,0 +1,11 @@
# Frobnitz
This is an example chart.
## Usage
This is an example. It has no usage.
## Development
For developer info, see the top-level repository.

@ -0,0 +1 @@
This should be ignored by the loader, but may be included in a chart.

@ -0,0 +1,4 @@
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0
home: https://k8s.io/helm

@ -0,0 +1,9 @@
This example was generated using the command `helm create alpine`.
The `templates/` directory contains a very simple pod resource with a
couple of parameters.
The `values.toml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install docs/examples/alpine`.

@ -0,0 +1,4 @@
name: mast1
description: A Helm chart for Kubernetes
version: 0.1.0
home: ""

@ -0,0 +1,4 @@
# Default values for mast1.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name = "value"

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: {{.Release.Name}}-{{.Chart.Name}}
labels:
heritage: {{.Release.Service}}
chartName: {{.Chart.Name}}
chartVersion: {{.Chart.Version | quote}}
spec:
restartPolicy: {{default "Never" .restart_policy}}
containers:
- name: waiter
image: "alpine:3.3"
command: ["/bin/sleep","9000"]

@ -0,0 +1,2 @@
# The pod name
name: "my-alpine"

@ -0,0 +1 @@
This is a placeholder for documentation.

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0" width="256" height="256" id="test">
<desc>Example icon</desc>
<rect id="first" x="2" y="2" width="40" height="60" fill="navy"/>
<rect id="second" x="15" y="4" width="40" height="60" fill="red"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

@ -0,0 +1,8 @@
dependencies:
- name: alpine
version: "0.1.0"
repository: https://example.com/charts
- name: mariner
version: "4.3.2"
repository: https://example.com/charts
digest: invalid

@ -0,0 +1,7 @@
dependencies:
- name: alpine
version: "0.1.0"
repository: https://example.com/charts
- name: mariner
version: "4.3.2"
repository: https://example.com/charts

@ -0,0 +1 @@
Hello {{.Name | default "world"}}

@ -0,0 +1,6 @@
# A values file contains configuration.
name: "Some Name"
section:
name: "Name in a section"

@ -0,0 +1,20 @@
apiVersion: v1
name: frobnitz_backslash
description: This is a frobnitz.
version: "1.2.3"
keywords:
- frobnitz
- sprocket
- dodad
maintainers:
- name: The Helm Team
email: helm@example.com
- name: Someone Else
email: nobody@example.com
sources:
- https://example.com/foo/bar
home: http://example.com
icon: https://example.com/64x64.png
annotations:
extrakey: extravalue
anotherkey: anothervalue

@ -0,0 +1 @@
This is an install document. The client may display this.

@ -0,0 +1,11 @@
# Frobnitz
This is an example chart.
## Usage
This is an example. It has no usage.
## Development
For developer info, see the top-level repository.

@ -0,0 +1 @@
This should be ignored by the loader, but may be included in a chart.

@ -0,0 +1,4 @@
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0
home: https://k8s.io/helm

@ -0,0 +1,9 @@
This example was generated using the command `helm create alpine`.
The `templates/` directory contains a very simple pod resource with a
couple of parameters.
The `values.toml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install docs/examples/alpine`.

@ -0,0 +1,4 @@
name: mast1
description: A Helm chart for Kubernetes
version: 0.1.0
home: ""

@ -0,0 +1,4 @@
# Default values for mast1.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name = "value"

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: {{.Release.Name}}-{{.Chart.Name}}
labels:
heritage: {{.Release.Service}}
chartName: {{.Chart.Name}}
chartVersion: {{.Chart.Version | quote}}
spec:
restartPolicy: {{default "Never" .restart_policy}}
containers:
- name: waiter
image: "alpine:3.3"
command: ["/bin/sleep","9000"]

@ -0,0 +1,2 @@
# The pod name
name: "my-alpine"

@ -0,0 +1 @@
This is a placeholder for documentation.

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0" width="256" height="256" id="test">
<desc>Example icon</desc>
<rect id="first" x="2" y="2" width="40" height="60" fill="navy"/>
<rect id="second" x="15" y="4" width="40" height="60" fill="red"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

@ -0,0 +1,8 @@
dependencies:
- name: alpine
version: "0.1.0"
repository: https://example.com/charts
- name: mariner
version: "4.3.2"
repository: https://example.com/charts
digest: invalid

@ -0,0 +1,7 @@
dependencies:
- name: alpine
version: "0.1.0"
repository: https://example.com/charts
- name: mariner
version: "4.3.2"
repository: https://example.com/charts

@ -0,0 +1 @@
Hello {{.Name | default "world"}}

@ -0,0 +1,6 @@
# A values file contains configuration.
name: "Some Name"
section:
name: "Name in a section"

@ -0,0 +1,70 @@
/*
Copyright 2018 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chart
import "time"
// Dependency describes a chart upon which another chart depends.
//
// Dependencies can be used to express developer intent, or to capture the state
// of a chart.
type Dependency struct {
// Name is the name of the dependency.
//
// This must mach the name in the dependency's Chart.yaml.
Name string `json:"name"`
// Version is the version (range) of this chart.
//
// A lock file will always produce a single version, while a dependency
// may contain a semantic version range.
Version string `json:"version,omitempty"`
// The URL to the repository.
//
// Appending `index.yaml` to this string should result in a URL that can be
// used to fetch the repository index.
Repository string `json:"repository"`
// A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
Condition string `json:"condition,omitempty"`
// Tags can be used to group charts for enabling/disabling together
Tags []string `json:"tags,omitempty"`
// Enabled bool determines if chart should be loaded
Enabled bool `json:"enabled,omitempty"`
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
// string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values,omitempty"`
// Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty"`
}
// Requirements is a list of requirements for a chart.
//
// Requirements are charts upon which this chart depends. This expresses
// developer intent.
type Requirements struct {
Dependencies []*Dependency `json:"dependencies"`
}
// RequirementsLock is a lock file for requirements.
//
// It represents the state that the dependencies should be in.
type RequirementsLock struct {
// Genderated is the date the lock file was last generated.
Generated time.Time `json:"generated"`
// Digest is a hash of the requirements file used to generate it.
Digest string `json:"digest"`
// Dependencies is the list of dependencies that this lock file has locked.
Dependencies []*Dependency `json:"dependencies"`
}

@ -20,13 +20,14 @@ import (
"runtime" "runtime"
"k8s.io/apimachinery/pkg/version" "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes/scheme"
tversion "k8s.io/helm/pkg/version" tversion "k8s.io/helm/pkg/version"
) )
var ( var (
// DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). // DefaultVersionSet is the default version set, which includes only Core V1 ("v1").
DefaultVersionSet = NewVersionSet("v1") DefaultVersionSet = allKnownVersions()
// DefaultKubeVersion is the default kubernetes version // DefaultKubeVersion is the default kubernetes version
DefaultKubeVersion = &version.Info{ DefaultKubeVersion = &version.Info{
@ -37,6 +38,12 @@ var (
Compiler: runtime.Compiler, Compiler: runtime.Compiler,
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
} }
// DefaultCapabilities is the default set of capabilities.
DefaultCapabilities = &Capabilities{
APIVersions: DefaultVersionSet,
KubeVersion: DefaultKubeVersion,
}
) )
// Capabilities describes the capabilities of the Kubernetes cluster that Tiller is attached to. // Capabilities describes the capabilities of the Kubernetes cluster that Tiller is attached to.
@ -52,11 +59,11 @@ type Capabilities struct {
} }
// VersionSet is a set of Kubernetes API versions. // VersionSet is a set of Kubernetes API versions.
type VersionSet map[string]interface{} type VersionSet map[string]struct{}
// NewVersionSet creates a new version set from a list of strings. // NewVersionSet creates a new version set from a list of strings.
func NewVersionSet(apiVersions ...string) VersionSet { func NewVersionSet(apiVersions ...string) VersionSet {
vs := VersionSet{} vs := make(VersionSet)
for _, v := range apiVersions { for _, v := range apiVersions {
vs[v] = struct{}{} vs[v] = struct{}{}
} }
@ -70,3 +77,11 @@ func (v VersionSet) Has(apiVersion string) bool {
_, ok := v[apiVersion] _, ok := v[apiVersion]
return ok return ok
} }
func allKnownVersions() VersionSet {
vs := make(VersionSet)
for gvk := range scheme.Scheme.AllKnownTypes() {
vs[gvk.GroupVersion().String()] = struct{}{}
}
return vs
}

@ -38,9 +38,6 @@ func TestDefaultVersionSet(t *testing.T) {
if !DefaultVersionSet.Has("v1") { if !DefaultVersionSet.Has("v1") {
t.Error("Expected core v1 version set") t.Error("Expected core v1 version set")
} }
if d := len(DefaultVersionSet); d != 1 {
t.Errorf("Expected only one version, got %d", d)
}
} }
func TestCapabilities(t *testing.T) { func TestCapabilities(t *testing.T) {

@ -24,29 +24,18 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
) )
// APIVersionv1 is the API version number for version 1.
const APIVersionv1 = "v1"
// UnmarshalChartfile takes raw Chart.yaml data and unmarshals it.
func UnmarshalChartfile(data []byte) (*chart.Metadata, error) {
y := &chart.Metadata{}
err := yaml.Unmarshal(data, y)
if err != nil {
return nil, err
}
return y, nil
}
// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. // LoadChartfile loads a Chart.yaml file into a *chart.Metadata.
func LoadChartfile(filename string) (*chart.Metadata, error) { func LoadChartfile(filename string) (*chart.Metadata, error) {
b, err := ioutil.ReadFile(filename) b, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return UnmarshalChartfile(b) y := new(chart.Metadata)
err = yaml.Unmarshal(b, y)
return y, err
} }
// SaveChartfile saves the given metadata as a Chart.yaml file at the given path. // SaveChartfile saves the given metadata as a Chart.yaml file at the given path.
@ -80,8 +69,8 @@ func IsChartDir(dirName string) (bool, error) {
return false, errors.Errorf("cannot read Chart.Yaml in directory %q", dirName) return false, errors.Errorf("cannot read Chart.Yaml in directory %q", dirName)
} }
chartContent, err := UnmarshalChartfile(chartYamlContent) chartContent := new(chart.Metadata)
if err != nil { if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil {
return false, err return false, err
} }
if chartContent == nil { if chartContent == nil {

@ -19,7 +19,7 @@ package chartutil
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
) )
const testfile = "testdata/chartfiletest.yaml" const testfile = "testdata/chartfiletest.yaml"
@ -40,8 +40,8 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
} }
// Api instead of API because it was generated via protobuf. // Api instead of API because it was generated via protobuf.
if f.APIVersion != APIVersionv1 { if f.APIVersion != chart.APIVersionv1 {
t.Errorf("Expected API Version %q, got %q", APIVersionv1, f.APIVersion) t.Errorf("Expected API Version %q, got %q", chart.APIVersionv1, f.APIVersion)
} }
if f.Name != name { if f.Name != name {

@ -21,10 +21,12 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
) )
const ( const (
@ -294,7 +296,7 @@ Create chart name and version as used by the chart label.
// CreateFrom creates a new chart, but scaffolds it from the src chart. // CreateFrom creates a new chart, but scaffolds it from the src chart.
func CreateFrom(chartfile *chart.Metadata, dest, src string) error { func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
schart, err := Load(src) schart, err := loader.Load(src)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not load %s", src) return errors.Wrapf(err, "could not load %s", src)
} }
@ -304,12 +306,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
var updatedTemplates []*chart.File var updatedTemplates []*chart.File
for _, template := range schart.Templates { for _, template := range schart.Templates {
newData := Transform(string(template.Data), "<CHARTNAME>", schart.Metadata.Name) newData := transform(string(template.Data), schart.Name())
updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData}) updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData})
} }
schart.Templates = updatedTemplates schart.Templates = updatedTemplates
schart.Values = Transform(string(schart.Values), "<CHARTNAME>", schart.Metadata.Name) schart.Values = transform(string(schart.Values), schart.Name())
return SaveDir(schart, dest) return SaveDir(schart, dest)
} }
@ -378,27 +380,27 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) {
{ {
// ingress.yaml // ingress.yaml
path: filepath.Join(cdir, TemplatesDir, IngressFileName), path: filepath.Join(cdir, TemplatesDir, IngressFileName),
content: Transform(defaultIngress, "<CHARTNAME>", chartfile.Name), content: transform(defaultIngress, chartfile.Name),
}, },
{ {
// deployment.yaml // deployment.yaml
path: filepath.Join(cdir, TemplatesDir, DeploymentName), path: filepath.Join(cdir, TemplatesDir, DeploymentName),
content: Transform(defaultDeployment, "<CHARTNAME>", chartfile.Name), content: transform(defaultDeployment, chartfile.Name),
}, },
{ {
// service.yaml // service.yaml
path: filepath.Join(cdir, TemplatesDir, ServiceName), path: filepath.Join(cdir, TemplatesDir, ServiceName),
content: Transform(defaultService, "<CHARTNAME>", chartfile.Name), content: transform(defaultService, chartfile.Name),
}, },
{ {
// NOTES.txt // NOTES.txt
path: filepath.Join(cdir, TemplatesDir, NotesName), path: filepath.Join(cdir, TemplatesDir, NotesName),
content: Transform(defaultNotes, "<CHARTNAME>", chartfile.Name), content: transform(defaultNotes, chartfile.Name),
}, },
{ {
// _helpers.tpl // _helpers.tpl
path: filepath.Join(cdir, TemplatesDir, HelpersName), path: filepath.Join(cdir, TemplatesDir, HelpersName),
content: Transform(defaultHelpers, "<CHARTNAME>", chartfile.Name), content: transform(defaultHelpers, chartfile.Name),
}, },
} }
@ -413,3 +415,9 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) {
} }
return cdir, nil return cdir, nil
} }
// transform performs a string replacement of the specified source for
// a given key with the replacement string
func transform(src, replacement string) []byte {
return []byte(strings.Replace(src, "<CHARTNAME>", replacement, -1))
}

@ -23,7 +23,8 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
) )
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
@ -42,13 +43,13 @@ func TestCreate(t *testing.T) {
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
mychart, err := LoadDir(c) mychart, err := loader.LoadDir(c)
if err != nil { if err != nil {
t.Fatalf("Failed to load newly created chart %q: %s", c, err) t.Fatalf("Failed to load newly created chart %q: %s", c, err)
} }
if mychart.Metadata.Name != "foo" { if mychart.Name() != "foo" {
t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name) t.Errorf("Expected name to be 'foo', got %q", mychart.Name())
} }
for _, d := range []string{TemplatesDir, ChartsDir} { for _, d := range []string{TemplatesDir, ChartsDir} {
@ -94,13 +95,13 @@ func TestCreateFrom(t *testing.T) {
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
c := filepath.Join(tdir, cf.Name) c := filepath.Join(tdir, cf.Name)
mychart, err := LoadDir(c) mychart, err := loader.LoadDir(c)
if err != nil { if err != nil {
t.Fatalf("Failed to load newly created chart %q: %s", c, err) t.Fatalf("Failed to load newly created chart %q: %s", c, err)
} }
if mychart.Metadata.Name != "foo" { if mychart.Name() != "foo" {
t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name) t.Errorf("Expected name to be 'foo', got %q", mychart.Name())
} }
for _, d := range []string{TemplatesDir, ChartsDir} { for _, d := range []string{TemplatesDir, ChartsDir} {
@ -111,7 +112,7 @@ func TestCreateFrom(t *testing.T) {
} }
} }
for _, f := range []string{ChartfileName, ValuesfileName, "requirements.yaml"} { for _, f := range []string{ChartfileName, ValuesfileName} {
if fi, err := os.Stat(filepath.Join(dir, f)); err != nil { if fi, err := os.Stat(filepath.Join(dir, f)); err != nil {
t.Errorf("Expected %s file: %s", f, err) t.Errorf("Expected %s file: %s", f, err)
} else if fi.IsDir() { } else if fi.IsDir() {

@ -16,7 +16,7 @@ limitations under the License.
/*Package chartutil contains tools for working with charts. /*Package chartutil contains tools for working with charts.
Charts are described in the protocol buffer definition (pkg/proto/hapi/charts). Charts are described in the protocol buffer definition (pkg/proto/charts).
This packe provides utilities for serializing and deserializing charts. This packe provides utilities for serializing and deserializing charts.
A chart can be represented on the file system in one of two ways: A chart can be represented on the file system in one of two ways:
@ -27,18 +27,18 @@ A chart can be represented on the file system in one of two ways:
This package provides utilitites for working with those file formats. This package provides utilitites for working with those file formats.
The preferred way of loading a chart is using 'chartutil.Load`: The preferred way of loading a chart is using 'loader.Load`:
chart, err := chartutil.Load(filename) chart, err := loader.Load(filename)
This will attempt to discover whether the file at 'filename' is a directory or This will attempt to discover whether the file at 'filename' is a directory or
a chart archive. It will then load accordingly. a chart archive. It will then load accordingly.
For accepting raw compressed tar file data from an io.Reader, the For accepting raw compressed tar file data from an io.Reader, the
'chartutil.LoadArchive()' will read in the data, uncompress it, and unpack it 'loader.LoadArchive()' will read in the data, uncompress it, and unpack it
into a Chart. into a Chart.
When creating charts in memory, use the 'k8s.io/helm/pkg/proto/hapi/chart' When creating charts in memory, use the 'k8s.io/helm/pkg/proto/chart'
package directly. package directly.
*/ */
package chartutil // import "k8s.io/helm/pkg/chartutil" package chartutil // import "k8s.io/helm/pkg/chartutil"

@ -40,8 +40,8 @@ func Expand(dir string, r io.Reader) error {
return err return err
} }
//split header name and create missing directories // split header name and create missing directories
d, _ := filepath.Split(header.Name) d := filepath.Dir(header.Name)
fullDir := filepath.Join(dir, d) fullDir := filepath.Join(dir, d)
_, err = os.Stat(fullDir) _, err = os.Stat(fullDir)
if err != nil && d != "" { if err != nil && d != "" {
@ -63,8 +63,7 @@ func Expand(dir string, r io.Reader) error {
if err != nil { if err != nil {
return err return err
} }
_, err = io.Copy(file, tr) if _, err = io.Copy(file, tr); err != nil {
if err != nil {
file.Close() file.Close()
return err return err
} }

@ -26,7 +26,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
) )
// Files is a map of files in a chart that can be accessed from a template. // Files is a map of files in a chart that can be accessed from a template.
@ -35,7 +35,7 @@ type Files map[string][]byte
// NewFiles creates a new Files from chart files. // NewFiles creates a new Files from chart files.
// Given an []*any.Any (the format for files in a chart.Chart), extract a map of files. // Given an []*any.Any (the format for files in a chart.Chart), extract a map of files.
func NewFiles(from []*chart.File) Files { func NewFiles(from []*chart.File) Files {
files := map[string][]byte{} files := make(map[string][]byte)
for _, f := range from { for _, f := range from {
files[f.Name] = f.Data files[f.Name] = f.Data
} }
@ -50,11 +50,10 @@ func NewFiles(from []*chart.File) Files {
// This is intended to be accessed from within a template, so a missed key returns // This is intended to be accessed from within a template, so a missed key returns
// an empty []byte. // an empty []byte.
func (f Files) GetBytes(name string) []byte { func (f Files) GetBytes(name string) []byte {
v, ok := f[name] if v, ok := f[name]; ok {
if !ok { return v
return []byte{}
} }
return v return []byte{}
} }
// Get returns a string representation of the given file. // Get returns a string representation of the given file.
@ -97,7 +96,7 @@ func (f Files) Glob(pattern string) Files {
// (regardless of path) should be unique. // (regardless of path) should be unique.
// //
// This is designed to be called from a template, and will return empty string // This is designed to be called from a template, and will return empty string
// (via ToYaml function) if it cannot be serialized to YAML, or if the Files // (via ToYAML function) if it cannot be serialized to YAML, or if the Files
// object is nil. // object is nil.
// //
// The output will not be indented, so you will want to pipe this to the // The output will not be indented, so you will want to pipe this to the
@ -110,14 +109,14 @@ func (f Files) AsConfig() string {
return "" return ""
} }
m := map[string]string{} m := make(map[string]string)
// Explicitly convert to strings, and file names // Explicitly convert to strings, and file names
for k, v := range f { for k, v := range f {
m[path.Base(k)] = string(v) m[path.Base(k)] = string(v)
} }
return ToYaml(m) return ToYAML(m)
} }
// AsSecrets returns the base64-encoded value of a Files object suitable for // AsSecrets returns the base64-encoded value of a Files object suitable for
@ -126,7 +125,7 @@ func (f Files) AsConfig() string {
// (regardless of path) should be unique. // (regardless of path) should be unique.
// //
// This is designed to be called from a template, and will return empty string // This is designed to be called from a template, and will return empty string
// (via ToYaml function) if it cannot be serialized to YAML, or if the Files // (via ToYAML function) if it cannot be serialized to YAML, or if the Files
// object is nil. // object is nil.
// //
// The output will not be indented, so you will want to pipe this to the // The output will not be indented, so you will want to pipe this to the
@ -139,13 +138,13 @@ func (f Files) AsSecrets() string {
return "" return ""
} }
m := map[string]string{} m := make(map[string]string)
for k, v := range f { for k, v := range f {
m[path.Base(k)] = base64.StdEncoding.EncodeToString(v) m[path.Base(k)] = base64.StdEncoding.EncodeToString(v)
} }
return ToYaml(m) return ToYAML(m)
} }
// Lines returns each line of a named file (split by "\n") as a slice, so it can // Lines returns each line of a named file (split by "\n") as a slice, so it can
@ -163,11 +162,11 @@ func (f Files) Lines(path string) []string {
return strings.Split(string(f[path]), "\n") return strings.Split(string(f[path]), "\n")
} }
// ToYaml takes an interface, marshals it to yaml, and returns a string. It will // ToYAML takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string). // always return a string, even on marshal error (empty string).
// //
// This is designed to be called from a template. // This is designed to be called from a template.
func ToYaml(v interface{}) string { func ToYAML(v interface{}) string {
data, err := yaml.Marshal(v) data, err := yaml.Marshal(v)
if err != nil { if err != nil {
// Swallow errors inside of a template. // Swallow errors inside of a template.
@ -176,13 +175,13 @@ func ToYaml(v interface{}) string {
return strings.TrimSuffix(string(data), "\n") return strings.TrimSuffix(string(data), "\n")
} }
// FromYaml converts a YAML document into a map[string]interface{}. // FromYAML converts a YAML document into a map[string]interface{}.
// //
// This is not a general-purpose YAML parser, and will not parse all valid // This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates // YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into // it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map. // m["Error"] in the returned map.
func FromYaml(str string) map[string]interface{} { func FromYAML(str string) map[string]interface{} {
m := map[string]interface{}{} m := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(str), &m); err != nil { if err := yaml.Unmarshal([]byte(str), &m); err != nil {
@ -191,11 +190,11 @@ func FromYaml(str string) map[string]interface{} {
return m return m
} }
// ToToml takes an interface, marshals it to toml, and returns a string. It will // ToTOML takes an interface, marshals it to toml, and returns a string. It will
// always return a string, even on marshal error (empty string). // always return a string, even on marshal error (empty string).
// //
// This is designed to be called from a template. // This is designed to be called from a template.
func ToToml(v interface{}) string { func ToTOML(v interface{}) string {
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
e := toml.NewEncoder(b) e := toml.NewEncoder(b)
err := e.Encode(v) err := e.Encode(v)
@ -205,11 +204,11 @@ func ToToml(v interface{}) string {
return b.String() return b.String()
} }
// ToJson takes an interface, marshals it to json, and returns a string. It will // ToJSON takes an interface, marshals it to json, and returns a string. It will
// always return a string, even on marshal error (empty string). // always return a string, even on marshal error (empty string).
// //
// This is designed to be called from a template. // This is designed to be called from a template.
func ToJson(v interface{}) string { func ToJSON(v interface{}) string {
data, err := json.Marshal(v) data, err := json.Marshal(v)
if err != nil { if err != nil {
// Swallow errors inside of a template. // Swallow errors inside of a template.
@ -218,14 +217,14 @@ func ToJson(v interface{}) string {
return string(data) return string(data)
} }
// FromJson converts a JSON document into a map[string]interface{}. // FromJSON converts a JSON document into a map[string]interface{}.
// //
// This is not a general-purpose JSON parser, and will not parse all valid // This is not a general-purpose JSON parser, and will not parse all valid
// JSON documents. Additionally, because its intended use is within templates // JSON documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into // it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map. // m["Error"] in the returned map.
func FromJson(str string) map[string]interface{} { func FromJSON(str string) map[string]interface{} {
m := map[string]interface{}{} m := make(map[string]interface{})
if err := json.Unmarshal([]byte(str), &m); err != nil { if err := json.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error() m["Error"] = err.Error()

@ -97,7 +97,7 @@ func TestLines(t *testing.T) {
as.Equal("bar", out[0]) as.Equal("bar", out[0])
} }
func TestToYaml(t *testing.T) { func TestToYAML(t *testing.T) {
expect := "foo: bar" expect := "foo: bar"
v := struct { v := struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -105,12 +105,12 @@ func TestToYaml(t *testing.T) {
Foo: "bar", Foo: "bar",
} }
if got := ToYaml(v); got != expect { if got := ToYAML(v); got != expect {
t.Errorf("Expected %q, got %q", expect, got) t.Errorf("Expected %q, got %q", expect, got)
} }
} }
func TestToToml(t *testing.T) { func TestToTOML(t *testing.T) {
expect := "foo = \"bar\"\n" expect := "foo = \"bar\"\n"
v := struct { v := struct {
Foo string `toml:"foo"` Foo string `toml:"foo"`
@ -118,7 +118,7 @@ func TestToToml(t *testing.T) {
Foo: "bar", Foo: "bar",
} }
if got := ToToml(v); got != expect { if got := ToTOML(v); got != expect {
t.Errorf("Expected %q, got %q", expect, got) t.Errorf("Expected %q, got %q", expect, got)
} }
@ -128,19 +128,19 @@ func TestToToml(t *testing.T) {
"sail": "white", "sail": "white",
}, },
} }
got := ToToml(dict) got := ToTOML(dict)
expect = "[mast]\n sail = \"white\"\n" expect = "[mast]\n sail = \"white\"\n"
if got != expect { if got != expect {
t.Errorf("Expected:\n%s\nGot\n%s\n", expect, got) t.Errorf("Expected:\n%s\nGot\n%s\n", expect, got)
} }
} }
func TestFromYaml(t *testing.T) { func TestFromYAML(t *testing.T) {
doc := `hello: world doc := `hello: world
one: one:
two: three two: three
` `
dict := FromYaml(doc) dict := FromYAML(doc)
if err, ok := dict["Error"]; ok { if err, ok := dict["Error"]; ok {
t.Fatalf("Parse error: %s", err) t.Fatalf("Parse error: %s", err)
} }
@ -160,13 +160,13 @@ one:
- two - two
- three - three
` `
dict = FromYaml(doc2) dict = FromYAML(doc2)
if _, ok := dict["Error"]; !ok { if _, ok := dict["Error"]; !ok {
t.Fatal("Expected parser error") t.Fatal("Expected parser error")
} }
} }
func TestToJson(t *testing.T) { func TestToJSON(t *testing.T) {
expect := `{"foo":"bar"}` expect := `{"foo":"bar"}`
v := struct { v := struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -174,12 +174,12 @@ func TestToJson(t *testing.T) {
Foo: "bar", Foo: "bar",
} }
if got := ToJson(v); got != expect { if got := ToJSON(v); got != expect {
t.Errorf("Expected %q, got %q", expect, got) t.Errorf("Expected %q, got %q", expect, got)
} }
} }
func TestFromJson(t *testing.T) { func TestFromJSON(t *testing.T) {
doc := `{ doc := `{
"hello": "world", "hello": "world",
"one": { "one": {
@ -187,7 +187,7 @@ func TestFromJson(t *testing.T) {
} }
} }
` `
dict := FromJson(doc) dict := FromJSON(doc)
if err, ok := dict["Error"]; ok { if err, ok := dict["Error"]; ok {
t.Fatalf("Parse error: %s", err) t.Fatalf("Parse error: %s", err)
} }
@ -209,7 +209,7 @@ func TestFromJson(t *testing.T) {
"three" "three"
] ]
` `
dict = FromJson(doc2) dict = FromJSON(doc2)
if _, ok := dict["Error"]; !ok { if _, ok := dict["Error"]; !ok {
t.Fatal("Expected parser error") t.Fatal("Expected parser error")
} }

@ -1,286 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/ignore"
"k8s.io/helm/pkg/sympath"
)
// Load takes a string name, tries to resolve it to a file or directory, and then loads it.
//
// This is the preferred way to load a chart. It will discover the chart encoding
// and hand off to the appropriate chart reader.
//
// If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive.
func Load(name string) (*chart.Chart, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if fi.IsDir() {
if validChart, err := IsChartDir(name); !validChart {
return nil, err
}
return LoadDir(name)
}
return LoadFile(name)
}
// BufferedFile represents an archive file buffered for later processing.
type BufferedFile struct {
Name string
Data []byte
}
// LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (*chart.Chart, error) {
unzipped, err := gzip.NewReader(in)
if err != nil {
return &chart.Chart{}, err
}
defer unzipped.Close()
files := []*BufferedFile{}
tr := tar.NewReader(unzipped)
for {
b := bytes.NewBuffer(nil)
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return &chart.Chart{}, err
}
if hd.FileInfo().IsDir() {
// Use this instead of hd.Typeflag because we don't have to do any
// inference chasing.
continue
}
// Archive could contain \ if generated on Windows
delimiter := "/"
if strings.ContainsRune(hd.Name, '\\') {
delimiter = "\\"
}
parts := strings.Split(hd.Name, delimiter)
n := strings.Join(parts[1:], delimiter)
// Normalize the path to the / delimiter
n = strings.Replace(n, delimiter, "/", -1)
if parts[0] == "Chart.yaml" {
return nil, errors.New("chart yaml not in base directory")
}
if _, err := io.Copy(b, tr); err != nil {
return &chart.Chart{}, err
}
files = append(files, &BufferedFile{Name: n, Data: b.Bytes()})
b.Reset()
}
if len(files) == 0 {
return nil, errors.New("no files in chart archive")
}
return LoadFiles(files)
}
// LoadFiles loads from in-memory files.
func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
c := &chart.Chart{}
subcharts := map[string][]*BufferedFile{}
for _, f := range files {
if f.Name == "Chart.yaml" {
m, err := UnmarshalChartfile(f.Data)
if err != nil {
return c, err
}
c.Metadata = m
} else if f.Name == "values.toml" {
return c, errors.New("values.toml is illegal as of 2.0.0-alpha.2")
} else if f.Name == "values.yaml" {
c.Values = f.Data
} else if strings.HasPrefix(f.Name, "templates/") {
c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data})
} else if strings.HasPrefix(f.Name, "charts/") {
if filepath.Ext(f.Name) == ".prov" {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
continue
}
cname := strings.TrimPrefix(f.Name, "charts/")
if strings.IndexAny(cname, "._") == 0 {
// Ignore charts/ that start with . or _.
continue
}
parts := strings.SplitN(cname, "/", 2)
scname := parts[0]
subcharts[scname] = append(subcharts[scname], &BufferedFile{Name: cname, Data: f.Data})
} else {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
}
}
// Ensure that we got a Chart.yaml file
if c.Metadata == nil {
return c, errors.New("chart metadata (Chart.yaml) missing")
}
if c.Metadata.Name == "" {
return c, errors.New("invalid chart (Chart.yaml): name must not be empty")
}
for n, files := range subcharts {
var sc *chart.Chart
var err error
if strings.IndexAny(n, "_.") == 0 {
continue
} else if filepath.Ext(n) == ".tgz" {
file := files[0]
if file.Name != n {
return c, errors.Errorf("error unpacking tar in %s: expected %s, got %s", c.Metadata.Name, n, file.Name)
}
// Untar the chart and add to c.Dependencies
b := bytes.NewBuffer(file.Data)
sc, err = LoadArchive(b)
} else {
// We have to trim the prefix off of every file, and ignore any file
// that is in charts/, but isn't actually a chart.
buff := make([]*BufferedFile, 0, len(files))
for _, f := range files {
parts := strings.SplitN(f.Name, "/", 2)
if len(parts) < 2 {
continue
}
f.Name = parts[1]
buff = append(buff, f)
}
sc, err = LoadFiles(buff)
}
if err != nil {
return c, errors.Wrapf(err, "error unpacking %s in %s", n, c.Metadata.Name)
}
c.Dependencies = append(c.Dependencies, sc)
}
return c, nil
}
// LoadFile loads from an archive file.
func LoadFile(name string) (*chart.Chart, error) {
if fi, err := os.Stat(name); err != nil {
return nil, err
} else if fi.IsDir() {
return nil, errors.New("cannot load a directory")
}
raw, err := os.Open(name)
if err != nil {
return nil, err
}
defer raw.Close()
return LoadArchive(raw)
}
// LoadDir loads from a directory.
//
// This loads charts only from directories.
func LoadDir(dir string) (*chart.Chart, error) {
topdir, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
// Just used for errors.
c := &chart.Chart{}
rules := ignore.Empty()
ifile := filepath.Join(topdir, ignore.HelmIgnore)
if _, err := os.Stat(ifile); err == nil {
r, err := ignore.ParseFile(ifile)
if err != nil {
return c, err
}
rules = r
}
rules.AddDefaults()
files := []*BufferedFile{}
topdir += string(filepath.Separator)
walk := func(name string, fi os.FileInfo, err error) error {
n := strings.TrimPrefix(name, topdir)
if n == "" {
// No need to process top level. Avoid bug with helmignore .* matching
// empty names. See issue 1779.
return nil
}
// Normalize to / since it will also work on Windows
n = filepath.ToSlash(n)
if err != nil {
return err
}
if fi.IsDir() {
// Directory-based ignore rules should involve skipping the entire
// contents of that directory.
if rules.Ignore(n, fi) {
return filepath.SkipDir
}
return nil
}
// If a .helmignore file matches, skip this file.
if rules.Ignore(n, fi) {
return nil
}
data, err := ioutil.ReadFile(name)
if err != nil {
return errors.Wrapf(err, "error reading %s", n)
}
files = append(files, &BufferedFile{Name: n, Data: data})
return nil
}
if err = sympath.Walk(topdir, walk); err != nil {
return c, err
}
return LoadFiles(files)
}

@ -18,209 +18,88 @@ package chartutil
import ( import (
"log" "log"
"strings" "strings"
"time"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/pkg/errors"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
const (
requirementsName = "requirements.yaml"
lockfileName = "requirements.lock"
)
var (
// ErrRequirementsNotFound indicates that a requirements.yaml is not found.
ErrRequirementsNotFound = errors.New(requirementsName + " not found")
// ErrLockfileNotFound indicates that a requirements.lock is not found.
ErrLockfileNotFound = errors.New(lockfileName + " not found")
)
// Dependency describes a chart upon which another chart depends.
//
// Dependencies can be used to express developer intent, or to capture the state
// of a chart.
type Dependency struct {
// Name is the name of the dependency.
//
// This must mach the name in the dependency's Chart.yaml.
Name string `json:"name"`
// Version is the version (range) of this chart.
//
// A lock file will always produce a single version, while a dependency
// may contain a semantic version range.
Version string `json:"version,omitempty"`
// The URL to the repository.
//
// Appending `index.yaml` to this string should result in a URL that can be
// used to fetch the repository index.
Repository string `json:"repository"`
// A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
Condition string `json:"condition,omitempty"`
// Tags can be used to group charts for enabling/disabling together
Tags []string `json:"tags,omitempty"`
// Enabled bool determines if chart should be loaded
Enabled bool `json:"enabled,omitempty"`
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
// string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values,omitempty"`
// Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty"`
}
// ErrNoRequirementsFile to detect error condition
type ErrNoRequirementsFile error
// Requirements is a list of requirements for a chart.
//
// Requirements are charts upon which this chart depends. This expresses
// developer intent.
type Requirements struct {
Dependencies []*Dependency `json:"dependencies"`
}
// RequirementsLock is a lock file for requirements.
//
// It represents the state that the dependencies should be in.
type RequirementsLock struct {
// Genderated is the date the lock file was last generated.
Generated time.Time `json:"generated"`
// Digest is a hash of the requirements file used to generate it.
Digest string `json:"digest"`
// Dependencies is the list of dependencies that this lock file has locked.
Dependencies []*Dependency `json:"dependencies"`
}
// LoadRequirements loads a requirements file from an in-memory chart.
func LoadRequirements(c *chart.Chart) (*Requirements, error) {
var data []byte
for _, f := range c.Files {
if f.Name == requirementsName {
data = f.Data
}
}
if len(data) == 0 {
return nil, ErrRequirementsNotFound
}
r := &Requirements{}
return r, yaml.Unmarshal(data, r)
}
// LoadRequirementsLock loads a requirements lock file.
func LoadRequirementsLock(c *chart.Chart) (*RequirementsLock, error) {
var data []byte
for _, f := range c.Files {
if f.Name == lockfileName {
data = f.Data
}
}
if len(data) == 0 {
return nil, ErrLockfileNotFound
}
r := &RequirementsLock{}
return r, yaml.Unmarshal(data, r)
}
// ProcessRequirementsConditions disables charts based on condition path value in values // ProcessRequirementsConditions disables charts based on condition path value in values
func ProcessRequirementsConditions(reqs *Requirements, cvals Values) { func ProcessRequirementsConditions(reqs *chart.Requirements, cvals Values) {
var cond string if reqs == nil {
var conds []string
if reqs == nil || len(reqs.Dependencies) == 0 {
return return
} }
for _, r := range reqs.Dependencies { for _, r := range reqs.Dependencies {
var hasTrue, hasFalse bool var hasTrue, hasFalse bool
cond = r.Condition for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") {
// check for list if len(c) > 0 {
if len(cond) > 0 { // retrieve value
if strings.Contains(cond, ",") { vv, err := cvals.PathValue(c)
conds = strings.Split(strings.TrimSpace(cond), ",") if err == nil {
} else { // if not bool, warn
conds = []string{strings.TrimSpace(cond)} if bv, ok := vv.(bool); ok {
} if bv {
for _, c := range conds { hasTrue = true
if len(c) > 0 {
// retrieve value
vv, err := cvals.PathValue(c)
if err == nil {
// if not bool, warn
if bv, ok := vv.(bool); ok {
if bv {
hasTrue = true
} else {
hasFalse = true
}
} else { } else {
log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) hasFalse = true
} }
} else if _, ok := err.(ErrNoValue); !ok { } else {
// this is a real error log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name)
log.Printf("Warning: PathValue returned error %v", err)
}
if vv != nil {
// got first value, break loop
break
} }
} else if _, ok := err.(ErrNoValue); !ok {
// this is a real error
log.Printf("Warning: PathValue returned error %v", err)
}
if vv != nil {
// got first value, break loop
break
} }
}
if !hasTrue && hasFalse {
r.Enabled = false
} else if hasTrue {
r.Enabled = true
} }
} }
if !hasTrue && hasFalse {
r.Enabled = false
} else if hasTrue {
r.Enabled = true
}
} }
} }
// ProcessRequirementsTags disables charts based on tags in values // ProcessRequirementsTags disables charts based on tags in values
func ProcessRequirementsTags(reqs *Requirements, cvals Values) { func ProcessRequirementsTags(reqs *chart.Requirements, cvals Values) {
vt, err := cvals.Table("tags") if reqs == nil {
if err != nil {
return return
} }
if reqs == nil || len(reqs.Dependencies) == 0 { vt, err := cvals.Table("tags")
if err != nil {
return return
} }
for _, r := range reqs.Dependencies { for _, r := range reqs.Dependencies {
if len(r.Tags) > 0 { var hasTrue, hasFalse bool
tags := r.Tags for _, k := range r.Tags {
if b, ok := vt[k]; ok {
var hasTrue, hasFalse bool // if not bool, warn
for _, k := range tags { if bv, ok := b.(bool); ok {
if b, ok := vt[k]; ok { if bv {
// if not bool, warn hasTrue = true
if bv, ok := b.(bool); ok {
if bv {
hasTrue = true
} else {
hasFalse = true
}
} else { } else {
log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name) hasFalse = true
} }
} else {
log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name)
} }
} }
if !hasTrue && hasFalse { }
r.Enabled = false if !hasTrue && hasFalse {
} else if hasTrue || !hasTrue && !hasFalse { r.Enabled = false
r.Enabled = true } else if hasTrue || !hasTrue && !hasFalse {
r.Enabled = true
}
} }
} }
} }
func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Chart { func getAliasDependency(charts []*chart.Chart, aliasChart *chart.Dependency) *chart.Chart {
var chartFound chart.Chart var chartFound chart.Chart
for _, existingChart := range charts { for _, existingChart := range charts {
if existingChart == nil { if existingChart == nil {
@ -248,14 +127,7 @@ func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Ch
// ProcessRequirementsEnabled removes disabled charts from dependencies // ProcessRequirementsEnabled removes disabled charts from dependencies
func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error { func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error {
reqs, err := LoadRequirements(c) if c.Requirements == nil {
if err != nil {
// if not just missing requirements file, return error
if nerr, ok := err.(ErrNoRequirementsFile); !ok {
return nerr
}
// no requirements to process
return nil return nil
} }
@ -265,9 +137,9 @@ func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error {
// However, if the dependency is already specified in requirements.yaml // However, if the dependency is already specified in requirements.yaml
// we should not add it, as it would be anyways processed from requirements.yaml // we should not add it, as it would be anyways processed from requirements.yaml
for _, existingDependency := range c.Dependencies { for _, existingDependency := range c.Dependencies() {
var dependencyFound bool var dependencyFound bool
for _, req := range reqs.Dependencies { for _, req := range c.Requirements.Dependencies {
if existingDependency.Metadata.Name == req.Name && version.IsCompatibleRange(req.Version, existingDependency.Metadata.Version) { if existingDependency.Metadata.Name == req.Name && version.IsCompatibleRange(req.Version, existingDependency.Metadata.Version) {
dependencyFound = true dependencyFound = true
break break
@ -278,18 +150,18 @@ func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error {
} }
} }
for _, req := range reqs.Dependencies { for _, req := range c.Requirements.Dependencies {
if chartDependency := getAliasDependency(c.Dependencies, req); chartDependency != nil { if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil {
chartDependencies = append(chartDependencies, chartDependency) chartDependencies = append(chartDependencies, chartDependency)
} }
if req.Alias != "" { if req.Alias != "" {
req.Name = req.Alias req.Name = req.Alias
} }
} }
c.Dependencies = chartDependencies c.SetDependencies(chartDependencies...)
// set all to true // set all to true
for _, lr := range reqs.Dependencies { for _, lr := range c.Requirements.Dependencies {
lr.Enabled = true lr.Enabled = true
} }
cvals, err := CoalesceValues(c, v) cvals, err := CoalesceValues(c, v)
@ -302,34 +174,32 @@ func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error {
return err return err
} }
// flag dependencies as enabled/disabled // flag dependencies as enabled/disabled
ProcessRequirementsTags(reqs, cvals) ProcessRequirementsTags(c.Requirements, cvals)
ProcessRequirementsConditions(reqs, cvals) ProcessRequirementsConditions(c.Requirements, cvals)
// make a map of charts to remove // make a map of charts to remove
rm := map[string]bool{} rm := map[string]struct{}{}
for _, r := range reqs.Dependencies { for _, r := range c.Requirements.Dependencies {
if !r.Enabled { if !r.Enabled {
// remove disabled chart // remove disabled chart
rm[r.Name] = true rm[r.Name] = struct{}{}
} }
} }
// don't keep disabled charts in new slice // don't keep disabled charts in new slice
cd := []*chart.Chart{} cd := []*chart.Chart{}
copy(cd, c.Dependencies[:0]) copy(cd, c.Dependencies()[:0])
for _, n := range c.Dependencies { for _, n := range c.Dependencies() {
if _, ok := rm[n.Metadata.Name]; !ok { if _, ok := rm[n.Metadata.Name]; !ok {
cd = append(cd, n) cd = append(cd, n)
} }
} }
// recursively call self to process sub dependencies // recursively call self to process sub dependencies
for _, t := range cd { for _, t := range cd {
err := ProcessRequirementsEnabled(t, yvals) if err := ProcessRequirementsEnabled(t, yvals); err != nil {
// if its not just missing requirements file, return error return err
if nerr, ok := err.(ErrNoRequirementsFile); !ok && err != nil {
return nerr
} }
} }
c.Dependencies = cd c.SetDependencies(cd...)
return nil return nil
} }
@ -361,30 +231,13 @@ func pathToMap(path string, data map[string]interface{}) map[string]interface{}
n[i][k] = n[z] n[i][k] = n[z]
} }
} }
return n[0] return n[0]
} }
// getParents returns a slice of parent charts in reverse order.
func getParents(c *chart.Chart, out []*chart.Chart) []*chart.Chart {
if len(out) == 0 {
out = []*chart.Chart{c}
}
for _, ch := range c.Dependencies {
if len(ch.Dependencies) > 0 {
out = append(out, ch)
out = getParents(ch, out)
}
}
return out
}
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. // processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
func processImportValues(c *chart.Chart) error { func processImportValues(c *chart.Chart) error {
reqs, err := LoadRequirements(c) if c.Requirements == nil {
if err != nil { return nil
return err
} }
// combine chart values and empty config to get Values // combine chart values and empty config to get Values
cvals, err := CoalesceValues(c, []byte{}) cvals, err := CoalesceValues(c, []byte{})
@ -393,45 +246,41 @@ func processImportValues(c *chart.Chart) error {
} }
b := make(map[string]interface{}) b := make(map[string]interface{})
// import values from each dependency if specified in import-values // import values from each dependency if specified in import-values
for _, r := range reqs.Dependencies { for _, r := range c.Requirements.Dependencies {
if len(r.ImportValues) > 0 { var outiv []interface{}
var outiv []interface{} for _, riv := range r.ImportValues {
for _, riv := range r.ImportValues { switch iv := riv.(type) {
switch iv := riv.(type) { case map[string]interface{}:
case map[string]interface{}: nm := map[string]string{
nm := map[string]string{ "child": iv["child"].(string),
"child": iv["child"].(string), "parent": iv["parent"].(string),
"parent": iv["parent"].(string), }
} outiv = append(outiv, nm)
outiv = append(outiv, nm) // get child table
s := r.Name + "." + nm["child"] vv, err := cvals.Table(r.Name + "." + nm["child"])
// get child table if err != nil {
vv, err := cvals.Table(s) log.Printf("Warning: ImportValues missing table: %v", err)
if err != nil { continue
log.Printf("Warning: ImportValues missing table: %v", err) }
continue // create value map from child to be merged into parent
} vm := pathToMap(nm["parent"], vv.AsMap())
// create value map from child to be merged into parent b = coalesceTables(cvals, vm)
vm := pathToMap(nm["parent"], vv.AsMap()) case string:
b = coalesceTables(cvals, vm) nm := map[string]string{
case string: "child": "exports." + iv,
nm := map[string]string{ "parent": ".",
"child": "exports." + iv,
"parent": ".",
}
outiv = append(outiv, nm)
s := r.Name + "." + nm["child"]
vm, err := cvals.Table(s)
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
b = coalesceTables(b, vm.AsMap())
} }
outiv = append(outiv, nm)
vm, err := cvals.Table(r.Name + "." + nm["child"])
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
b = coalesceTables(b, vm.AsMap())
} }
// set our formatted import values
r.ImportValues = outiv
} }
// set our formatted import values
r.ImportValues = outiv
} }
b = coalesceTables(b, cvals) b = coalesceTables(b, cvals)
y, err := yaml.Marshal(b) y, err := yaml.Marshal(b)
@ -447,10 +296,11 @@ func processImportValues(c *chart.Chart) error {
// ProcessRequirementsImportValues imports specified chart values from child to parent. // ProcessRequirementsImportValues imports specified chart values from child to parent.
func ProcessRequirementsImportValues(c *chart.Chart) error { func ProcessRequirementsImportValues(c *chart.Chart) error {
pc := getParents(c, nil) for _, d := range c.Dependencies() {
for i := len(pc) - 1; i >= 0; i-- { // recurse
processImportValues(pc[i]) if err := ProcessRequirementsImportValues(d); err != nil {
return err
}
} }
return processImportValues(c)
return nil
} }

@ -20,12 +20,13 @@ import (
"strconv" "strconv"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
func TestLoadRequirements(t *testing.T) { func TestLoadRequirements(t *testing.T) {
c, err := Load("testdata/frobnitz") c, err := loader.Load("testdata/frobnitz")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
@ -33,158 +34,89 @@ func TestLoadRequirements(t *testing.T) {
} }
func TestLoadRequirementsLock(t *testing.T) { func TestLoadRequirementsLock(t *testing.T) {
c, err := Load("testdata/frobnitz") c, err := loader.Load("testdata/frobnitz")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
verifyRequirementsLock(t, c) verifyRequirementsLock(t, c)
} }
func TestRequirementsTagsNonValue(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags with no effect
v := []byte("tags:\n nothinguseful: false\n\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1", "subcharta", "subchartb"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsTagsDisabledL1(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags disabling a group
v := []byte("tags:\n front-end: false\n\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsTagsEnabledL1(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags disabling a group and enabling a different group
v := []byte("tags:\n front-end: false\n\n back-end: true\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart2", "subchartb", "subchartc"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsTagsDisabledL2(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags disabling only children, children still enabled since tag front-end=true in values.yaml
v := []byte("tags:\n subcharta: false\n\n subchartb: false\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1", "subcharta", "subchartb"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsTagsDisabledL1Mixed(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags disabling all parents/children with additional tag re-enabling a parent
v := []byte("tags:\n front-end: false\n\n subchart1: true\n\n back-end: false\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsConditionsNonValue(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags with no effect
v := []byte("subchart1:\n nothinguseful: false\n\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1", "subcharta", "subchartb"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsConditionsEnabledL1Both(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml
v := []byte("subchart1:\n enabled: true\nsubchart2:\n enabled: true\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsConditionsDisabledL1Both(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// conditions disabling the parent charts, effectively disabling children
v := []byte("subchart1:\n enabled: false\nsubchart2:\n enabled: false\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsConditionsSecond(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// conditions a child using the second condition path of child's condition
v := []byte("subchart1:\n subcharta:\n enabled: false\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1", "subchartb"}
verifyRequirementsEnabled(t, c, v, e)
}
func TestRequirementsCombinedDisabledL2(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
// tags enabling a parent/child group with condition disabling one child
v := []byte("subchartc:\n enabled: false\ntags:\n back-end: true\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb", "subchartb"}
verifyRequirementsEnabled(t, c, v, e) func TestRequirementsEnabled(t *testing.T) {
} tests := []struct {
func TestRequirementsCombinedDisabledL1(t *testing.T) { name string
c, err := Load("testdata/subpop") v []byte
if err != nil { e []string // expected charts including duplicates in alphanumeric order
t.Fatalf("Failed to load testdata: %s", err) }{{
"tags with no effect",
[]byte("tags:\n nothinguseful: false\n\n"),
[]string{"parentchart", "subchart1", "subcharta", "subchartb"},
}, {
"tags with no effect",
[]byte("tags:\n nothinguseful: false\n\n"),
[]string{"parentchart", "subchart1", "subcharta", "subchartb"},
}, {
"tags disabling a group",
[]byte("tags:\n front-end: false\n\n"),
[]string{"parentchart"},
}, {
"tags disabling a group and enabling a different group",
[]byte("tags:\n front-end: false\n\n back-end: true\n"),
[]string{"parentchart", "subchart2", "subchartb", "subchartc"},
}, {
"tags disabling only children, children still enabled since tag front-end=true in values.yaml",
[]byte("tags:\n subcharta: false\n\n subchartb: false\n"),
[]string{"parentchart", "subchart1", "subcharta", "subchartb"},
}, {
"tags disabling all parents/children with additional tag re-enabling a parent",
[]byte("tags:\n front-end: false\n\n subchart1: true\n\n back-end: false\n"),
[]string{"parentchart", "subchart1"},
}, {
"tags with no effect",
[]byte("subchart1:\n nothinguseful: false\n\n"),
[]string{"parentchart", "subchart1", "subcharta", "subchartb"},
}, {
"conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml",
[]byte("subchart1:\n enabled: true\nsubchart2:\n enabled: true\n"),
[]string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb"},
}, {
"conditions disabling the parent charts, effectively disabling children",
[]byte("subchart1:\n enabled: false\nsubchart2:\n enabled: false\n"),
[]string{"parentchart"},
}, {
"conditions a child using the second condition path of child's condition",
[]byte("subchart1:\n subcharta:\n enabled: false\n"),
[]string{"parentchart", "subchart1", "subchartb"},
}, {
"tags enabling a parent/child group with condition disabling one child",
[]byte("subchartc:\n enabled: false\ntags:\n back-end: true\n"),
[]string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb", "subchartb"},
}, {
"tags will not enable a child if parent is explicitly disabled with condition",
[]byte("subchart1:\n enabled: false\ntags:\n front-end: true\n"),
[]string{"parentchart"},
}}
for _, tc := range tests {
c, err := loader.Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
t.Run(tc.name, func(t *testing.T) {
verifyRequirementsEnabled(t, c, tc.v, tc.e)
})
} }
// tags will not enable a child if parent is explicitly disabled with condition
v := []byte("subchart1:\n enabled: false\ntags:\n front-end: true\n")
// expected charts including duplicates in alphanumeric order
e := []string{"parentchart"}
verifyRequirementsEnabled(t, c, v, e)
} }
func verifyRequirementsEnabled(t *testing.T, c *chart.Chart, v []byte, e []string) { func verifyRequirementsEnabled(t *testing.T, c *chart.Chart, v []byte, e []string) {
out := []*chart.Chart{} if err := ProcessRequirementsEnabled(c, v); err != nil {
err := ProcessRequirementsEnabled(c, v)
if err != nil {
t.Errorf("Error processing enabled requirements %v", err) t.Errorf("Error processing enabled requirements %v", err)
} }
out = extractCharts(c, out)
out := extractCharts(c, nil)
// build list of chart names // build list of chart names
p := []string{} var p []string
for _, r := range out { for _, r := range out {
p = append(p, r.Metadata.Name) p = append(p, r.Name())
} }
//sort alphanumeric and compare to expectations //sort alphanumeric and compare to expectations
sort.Strings(p) sort.Strings(p)
@ -201,23 +133,21 @@ func verifyRequirementsEnabled(t *testing.T, c *chart.Chart, v []byte, e []strin
// extractCharts recursively searches chart dependencies returning all charts found // extractCharts recursively searches chart dependencies returning all charts found
func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart { func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart {
if len(c.Name()) > 0 {
if len(c.Metadata.Name) > 0 {
out = append(out, c) out = append(out, c)
} }
for _, d := range c.Dependencies { for _, d := range c.Dependencies() {
out = extractCharts(d, out) out = extractCharts(d, out)
} }
return out return out
} }
func TestProcessRequirementsImportValues(t *testing.T) { func TestProcessRequirementsImportValues(t *testing.T) {
c, err := Load("testdata/subpop") c, err := loader.Load("testdata/subpop")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
v := []byte{}
e := make(map[string]string) e := make(map[string]string)
e["imported-chart1.SC1bool"] = "true" e["imported-chart1.SC1bool"] = "true"
@ -279,17 +209,16 @@ func TestProcessRequirementsImportValues(t *testing.T) {
e["SCBexported2A"] = "blaster" e["SCBexported2A"] = "blaster"
e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" e["global.SC1exported2.all.SC1exported3"] = "SC1expstr"
verifyRequirementsImportValues(t, c, v, e) verifyRequirementsImportValues(t, c, e)
} }
func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v []byte, e map[string]string) {
err := ProcessRequirementsImportValues(c) func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, e map[string]string) {
if err != nil { if err := ProcessRequirementsImportValues(c); err != nil {
t.Errorf("Error processing import values requirements %v", err) t.Fatalf("Error processing import values requirements %v", err)
} }
cc, err := ReadValues(c.Values) cc, err := ReadValues(c.Values)
if err != nil { if err != nil {
t.Errorf("Error reading import values %v", err) t.Fatalf("Error reading import values %v", err)
} }
for kk, vv := range e { for kk, vv := range e {
pv, err := cc.PathValue(kk) pv, err := cc.PathValue(kk)
@ -317,182 +246,203 @@ func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v []byte, e ma
return return
} }
} }
} }
} }
func TestGetAliasDependency(t *testing.T) { func TestGetAliasDependency(t *testing.T) {
c, err := Load("testdata/frobnitz") c, err := loader.Load("testdata/frobnitz")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
req, err := LoadRequirements(c)
if err != nil { req := c.Requirements
t.Fatalf("Failed to load requirement for testdata: %s", err)
}
if len(req.Dependencies) == 0 { if len(req.Dependencies) == 0 {
t.Fatalf("There are no requirements to test") t.Fatalf("There are no requirements to test")
} }
// Success case // Success case
aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]) aliasChart := getAliasDependency(c.Dependencies(), req.Dependencies[0])
if aliasChart == nil { if aliasChart == nil {
t.Fatalf("Failed to get dependency chart for alias %s", req.Dependencies[0].Name) t.Fatalf("Failed to get dependency chart for alias %s", req.Dependencies[0].Name)
} }
if req.Dependencies[0].Alias != "" { if req.Dependencies[0].Alias != "" {
if aliasChart.Metadata.Name != req.Dependencies[0].Alias { if aliasChart.Name() != req.Dependencies[0].Alias {
t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Alias, aliasChart.Metadata.Name) t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Alias, aliasChart.Name())
} }
} else if aliasChart.Metadata.Name != req.Dependencies[0].Name { } else if aliasChart.Name() != req.Dependencies[0].Name {
t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.Name) t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Name())
} }
if req.Dependencies[0].Version != "" { if req.Dependencies[0].Version != "" {
if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) {
t.Fatalf("Dependency chart version is not in the compatible range") t.Fatalf("Dependency chart version is not in the compatible range")
} }
} }
// Failure case // Failure case
req.Dependencies[0].Name = "something-else" req.Dependencies[0].Name = "something-else"
if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { if aliasChart := getAliasDependency(c.Dependencies(), req.Dependencies[0]); aliasChart != nil {
t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) t.Fatalf("expected no chart but got %s", aliasChart.Name())
} }
req.Dependencies[0].Version = "something else which is not in the compatible range" req.Dependencies[0].Version = "something else which is not in the compatible range"
if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) {
t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ") t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ")
} }
} }
func TestDependentChartAliases(t *testing.T) { func TestDependentChartAliases(t *testing.T) {
c, err := Load("testdata/dependent-chart-alias") c, err := loader.Load("testdata/dependent-chart-alias")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
if len(c.Dependencies) == 0 { if len(c.Dependencies()) == 0 {
t.Fatal("There are no dependencies to run this test") t.Fatal("There are no dependencies to run this test")
} }
origLength := len(c.Dependencies) origLength := len(c.Dependencies())
if err := ProcessRequirementsEnabled(c, c.Values); err != nil { if err := ProcessRequirementsEnabled(c, c.Values); err != nil {
t.Fatalf("Expected no errors but got %q", err) t.Fatalf("Expected no errors but got %q", err)
} }
if len(c.Dependencies) == origLength { if len(c.Dependencies()) == origLength {
t.Fatal("Expected alias dependencies to be added, but did not got that") t.Fatal("Expected alias dependencies to be added, but did not got that")
} }
reqmts, err := LoadRequirements(c) if len(c.Dependencies()) != len(c.Requirements.Dependencies) {
if err != nil { t.Fatalf("Expected number of chart dependencies %d, but got %d", len(c.Requirements.Dependencies), len(c.Dependencies()))
t.Fatalf("Cannot load requirements for test chart, %v", err)
}
if len(c.Dependencies) != len(reqmts.Dependencies) {
t.Fatalf("Expected number of chart dependencies %d, but got %d", len(reqmts.Dependencies), len(c.Dependencies))
} }
} }
func TestDependentChartWithSubChartsAbsentInRequirements(t *testing.T) { func TestDependentChartWithSubChartsAbsentInRequirements(t *testing.T) {
c, err := Load("testdata/dependent-chart-no-requirements-yaml") c, err := loader.Load("testdata/dependent-chart-no-requirements-yaml")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
if len(c.Dependencies) != 2 { if len(c.Dependencies()) != 2 {
t.Fatalf("Expected 2 dependencies for this chart, but got %d", len(c.Dependencies)) t.Fatalf("Expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
} }
origLength := len(c.Dependencies) origLength := len(c.Dependencies())
if err := ProcessRequirementsEnabled(c, c.Values); err != nil { if err := ProcessRequirementsEnabled(c, c.Values); err != nil {
t.Fatalf("Expected no errors but got %q", err) t.Fatalf("Expected no errors but got %q", err)
} }
if len(c.Dependencies) != origLength { if len(c.Dependencies()) != origLength {
t.Fatal("Expected no changes in dependencies to be, but did something got changed") t.Fatal("Expected no changes in dependencies to be, but did something got changed")
} }
} }
func TestDependentChartWithSubChartsHelmignore(t *testing.T) { func TestDependentChartWithSubChartsHelmignore(t *testing.T) {
if _, err := Load("testdata/dependent-chart-helmignore"); err != nil { if _, err := loader.Load("testdata/dependent-chart-helmignore"); err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
} }
func TestDependentChartsWithSubChartsSymlink(t *testing.T) { func TestDependentChartsWithSubChartsSymlink(t *testing.T) {
c, err := Load("testdata/joonix") c, err := loader.Load("testdata/joonix")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
if c.Metadata.Name != "joonix" { if c.Name() != "joonix" {
t.Fatalf("Unexpected chart name: %s", c.Metadata.Name) t.Fatalf("Unexpected chart name: %s", c.Name())
} }
if n := len(c.Dependencies); n != 1 { if n := len(c.Dependencies()); n != 1 {
t.Fatalf("Expected 1 dependency for this chart, but got %d", n) t.Fatalf("Expected 1 dependency for this chart, but got %d", n)
} }
} }
func TestDependentChartsWithSubchartsAllSpecifiedInRequirements(t *testing.T) { func TestDependentChartsWithSubchartsAllSpecifiedInRequirements(t *testing.T) {
c, err := Load("testdata/dependent-chart-with-all-in-requirements-yaml") c, err := loader.Load("testdata/dependent-chart-with-all-in-requirements-yaml")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
if len(c.Dependencies) == 0 { if len(c.Dependencies()) == 0 {
t.Fatal("There are no dependencies to run this test") t.Fatal("There are no dependencies to run this test")
} }
origLength := len(c.Dependencies) origLength := len(c.Dependencies())
if err := ProcessRequirementsEnabled(c, c.Values); err != nil { if err := ProcessRequirementsEnabled(c, c.Values); err != nil {
t.Fatalf("Expected no errors but got %q", err) t.Fatalf("Expected no errors but got %q", err)
} }
if len(c.Dependencies) != origLength { if len(c.Dependencies()) != origLength {
t.Fatal("Expected no changes in dependencies to be, but did something got changed") t.Fatal("Expected no changes in dependencies to be, but did something got changed")
} }
reqmts, err := LoadRequirements(c) if len(c.Dependencies()) != len(c.Requirements.Dependencies) {
if err != nil { t.Fatalf("Expected number of chart dependencies %d, but got %d", len(c.Requirements.Dependencies), len(c.Dependencies()))
t.Fatalf("Cannot load requirements for test chart, %v", err)
}
if len(c.Dependencies) != len(reqmts.Dependencies) {
t.Fatalf("Expected number of chart dependencies %d, but got %d", len(reqmts.Dependencies), len(c.Dependencies))
} }
} }
func TestDependentChartsWithSomeSubchartsSpecifiedInRequirements(t *testing.T) { func TestDependentChartsWithSomeSubchartsSpecifiedInRequirements(t *testing.T) {
c, err := Load("testdata/dependent-chart-with-mixed-requirements-yaml") c, err := loader.Load("testdata/dependent-chart-with-mixed-requirements-yaml")
if err != nil { if err != nil {
t.Fatalf("Failed to load testdata: %s", err) t.Fatalf("Failed to load testdata: %s", err)
} }
if len(c.Dependencies) == 0 { if len(c.Dependencies()) == 0 {
t.Fatal("There are no dependencies to run this test") t.Fatal("There are no dependencies to run this test")
} }
origLength := len(c.Dependencies) origLength := len(c.Dependencies())
if err := ProcessRequirementsEnabled(c, c.Values); err != nil { if err := ProcessRequirementsEnabled(c, c.Values); err != nil {
t.Fatalf("Expected no errors but got %q", err) t.Fatalf("Expected no errors but got %q", err)
} }
if len(c.Dependencies) != origLength { if len(c.Dependencies()) != origLength {
t.Fatal("Expected no changes in dependencies to be, but did something got changed") t.Fatal("Expected no changes in dependencies to be, but did something got changed")
} }
reqmts, err := LoadRequirements(c) if len(c.Dependencies()) <= len(c.Requirements.Dependencies) {
if err != nil { t.Fatalf("Expected more dependencies than specified in requirements.yaml(%d), but got %d", len(c.Requirements.Dependencies), len(c.Dependencies()))
t.Fatalf("Cannot load requirements for test chart, %v", err)
} }
}
if len(c.Dependencies) <= len(reqmts.Dependencies) { func verifyRequirements(t *testing.T, c *chart.Chart) {
t.Fatalf("Expected more dependencies than specified in requirements.yaml(%d), but got %d", len(reqmts.Dependencies), len(c.Dependencies)) if len(c.Requirements.Dependencies) != 2 {
t.Errorf("Expected 2 requirements, got %d", len(c.Requirements.Dependencies))
}
tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
} }
for i, tt := range tests {
d := c.Requirements.Dependencies[i]
if d.Name != tt.Name {
t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name)
}
if d.Version != tt.Version {
t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
}
if d.Repository != tt.Repository {
t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
}
}
}
func verifyRequirementsLock(t *testing.T, c *chart.Chart) {
if len(c.Requirements.Dependencies) != 2 {
t.Errorf("Expected 2 requirements, got %d", len(c.Requirements.Dependencies))
}
tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
}
for i, tt := range tests {
d := c.Requirements.Dependencies[i]
if d.Name != tt.Name {
t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name)
}
if d.Version != tt.Version {
t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
}
if d.Repository != tt.Repository {
t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
}
}
} }

@ -27,7 +27,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
) )
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
@ -35,7 +35,7 @@ var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
// SaveDir saves a chart as files in a directory. // SaveDir saves a chart as files in a directory.
func SaveDir(c *chart.Chart, dest string) error { func SaveDir(c *chart.Chart, dest string) error {
// Create the chart directory // Create the chart directory
outdir := filepath.Join(dest, c.Metadata.Name) outdir := filepath.Join(dest, c.Name())
if err := os.Mkdir(outdir, 0755); err != nil { if err := os.Mkdir(outdir, 0755); err != nil {
return err return err
} }
@ -83,7 +83,7 @@ func SaveDir(c *chart.Chart, dest string) error {
// Save dependencies // Save dependencies
base := filepath.Join(outdir, ChartsDir) base := filepath.Join(outdir, ChartsDir)
for _, dep := range c.Dependencies { for _, dep := range c.Dependencies() {
// Here, we write each dependency as a tar file. // Here, we write each dependency as a tar file.
if _, err := Save(dep, base); err != nil { if _, err := Save(dep, base); err != nil {
return err return err
@ -158,7 +158,7 @@ func Save(c *chart.Chart, outDir string) (string, error) {
} }
func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
base := filepath.Join(prefix, c.Metadata.Name) base := filepath.Join(prefix, c.Name())
// Save Chart.yaml // Save Chart.yaml
cdata, err := yaml.Marshal(c.Metadata) cdata, err := yaml.Marshal(c.Metadata)
@ -193,7 +193,7 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
} }
// Save dependencies // Save dependencies
for _, dep := range c.Dependencies { for _, dep := range c.Dependencies() {
if err := writeTarContents(out, dep, base+"/charts"); err != nil { if err := writeTarContents(out, dep, base+"/charts"); err != nil {
return err return err
} }
@ -212,8 +212,6 @@ func writeToTar(out *tar.Writer, name string, body []byte) error {
if err := out.WriteHeader(h); err != nil { if err := out.WriteHeader(h); err != nil {
return err return err
} }
if _, err := out.Write(body); err != nil { _, err := out.Write(body)
return err return err
}
return nil
} }

@ -23,7 +23,8 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
) )
func TestSave(t *testing.T) { func TestSave(t *testing.T) {
@ -55,13 +56,13 @@ func TestSave(t *testing.T) {
t.Fatalf("Expected %q to end with .tgz", where) t.Fatalf("Expected %q to end with .tgz", where)
} }
c2, err := LoadFile(where) c2, err := loader.LoadFile(where)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c2.Metadata.Name != c.Metadata.Name { if c2.Name() != c.Name() {
t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name) t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
} }
if !bytes.Equal(c2.Values, c.Values) { if !bytes.Equal(c2.Values, c.Values) {
t.Fatal("Values data did not match") t.Fatal("Values data did not match")
@ -93,13 +94,13 @@ func TestSaveDir(t *testing.T) {
t.Fatalf("Failed to save: %s", err) t.Fatalf("Failed to save: %s", err)
} }
c2, err := LoadDir(tmp + "/ahab") c2, err := loader.LoadDir(tmp + "/ahab")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if c2.Metadata.Name != c.Metadata.Name { if c2.Name() != c.Name() {
t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name) t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
} }
if !bytes.Equal(c2.Values, c.Values) { if !bytes.Equal(c2.Values, c.Values) {
t.Fatal("Values data did not match") t.Fatal("Values data did not match")

@ -25,7 +25,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
) )
// ErrNoTable indicates that a chart does not have a matching table. // ErrNoTable indicates that a chart does not have a matching table.
@ -59,11 +59,10 @@ func (v Values) YAML() (string, error) {
// //
// An ErrNoTable is returned if the table does not exist. // An ErrNoTable is returned if the table does not exist.
func (v Values) Table(name string) (Values, error) { func (v Values) Table(name string) (Values, error) {
names := strings.Split(name, ".")
table := v table := v
var err error var err error
for _, n := range names { for _, n := range strings.Split(name, ".") {
table, err = tableLookup(table, n) table, err = tableLookup(table, n)
if err != nil { if err != nil {
return table, err return table, err
@ -110,7 +109,7 @@ func tableLookup(v Values, simple string) (Values, error) {
} }
var e ErrNoTable = errors.Errorf("no table named %q", simple) var e ErrNoTable = errors.Errorf("no table named %q", simple)
return map[string]interface{}{}, e return Values{}, e
} }
// ReadValues will parse YAML byte data into a Values. // ReadValues will parse YAML byte data into a Values.
@ -141,23 +140,23 @@ func ReadValuesFile(filename string) (Values, error) {
// - A chart has access to all of the variables for it, as well as all of // - A chart has access to all of the variables for it, as well as all of
// the values destined for its dependencies. // the values destined for its dependencies.
func CoalesceValues(chrt *chart.Chart, vals []byte) (Values, error) { func CoalesceValues(chrt *chart.Chart, vals []byte) (Values, error) {
var err error
cvals := Values{} cvals := Values{}
// Parse values if not nil. We merge these at the top level because // Parse values if not nil. We merge these at the top level because
// the passed-in values are in the same namespace as the parent chart. // the passed-in values are in the same namespace as the parent chart.
if vals != nil { if vals != nil {
evals, err := ReadValues(vals) cvals, err = ReadValues(vals)
if err != nil {
return cvals, err
}
cvals, err = coalesce(chrt, evals)
if err != nil { if err != nil {
return cvals, err return cvals, err
} }
} }
var err error cvals, err = coalesce(chrt, cvals)
cvals, err = coalesceDeps(chrt, cvals) if err != nil {
return cvals, err return cvals, err
}
return coalesceDeps(chrt, cvals)
} }
// coalesce coalesces the dest values and the chart values, giving priority to the dest values. // coalesce coalesces the dest values and the chart values, giving priority to the dest values.
@ -175,14 +174,14 @@ func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interfac
// coalesceDeps coalesces the dependencies of the given chart. // coalesceDeps coalesces the dependencies of the given chart.
func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
for _, subchart := range chrt.Dependencies { for _, subchart := range chrt.Dependencies() {
if c, ok := dest[subchart.Metadata.Name]; !ok { if c, ok := dest[subchart.Name()]; !ok {
// If dest doesn't already have the key, create it. // If dest doesn't already have the key, create it.
dest[subchart.Metadata.Name] = map[string]interface{}{} dest[subchart.Name()] = make(map[string]interface{})
} else if !istable(c) { } else if !istable(c) {
return dest, errors.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c) return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), c)
} }
if dv, ok := dest[subchart.Metadata.Name]; ok { if dv, ok := dest[subchart.Name()]; ok {
dvmap := dv.(map[string]interface{}) dvmap := dv.(map[string]interface{})
// Get globals out of dest and merge them into dvmap. // Get globals out of dest and merge them into dvmap.
@ -190,7 +189,7 @@ func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]in
var err error var err error
// Now coalesce the rest of the values. // Now coalesce the rest of the values.
dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap) dest[subchart.Name()], err = coalesce(subchart, dvmap)
if err != nil { if err != nil {
return dest, err return dest, err
} }
@ -206,14 +205,14 @@ func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
var dg, sg map[string]interface{} var dg, sg map[string]interface{}
if destglob, ok := dest[GlobalKey]; !ok { if destglob, ok := dest[GlobalKey]; !ok {
dg = map[string]interface{}{} dg = make(map[string]interface{})
} else if dg, ok = destglob.(map[string]interface{}); !ok { } else if dg, ok = destglob.(map[string]interface{}); !ok {
log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey) log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
return dg return dg
} }
if srcglob, ok := src[GlobalKey]; !ok { if srcglob, ok := src[GlobalKey]; !ok {
sg = map[string]interface{}{} sg = make(map[string]interface{})
} else if sg, ok = srcglob.(map[string]interface{}); !ok { } else if sg, ok = srcglob.(map[string]interface{}); !ok {
log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey) log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
return dg return dg
@ -340,19 +339,8 @@ type ReleaseOptions struct {
// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
// //
// WARNING: This function is deprecated for Helm > 2.1.99 Use ToRenderValuesCaps() instead. It will
// remain in the codebase to stay SemVer compliant.
//
// In Helm 3.0, this will be changed to accept Capabilities as a fourth parameter.
func ToRenderValues(chrt *chart.Chart, chrtVals []byte, options ReleaseOptions) (Values, error) {
caps := &Capabilities{APIVersions: DefaultVersionSet}
return ToRenderValuesCaps(chrt, chrtVals, options, caps)
}
// ToRenderValuesCaps composes the struct from the data coming from the Releases, Charts and Values files
//
// This takes both ReleaseOptions and Capabilities to merge into the render values. // This takes both ReleaseOptions and Capabilities to merge into the render values.
func ToRenderValuesCaps(chrt *chart.Chart, chrtVals []byte, options ReleaseOptions, caps *Capabilities) (Values, error) { func ToRenderValues(chrt *chart.Chart, chrtVals []byte, options ReleaseOptions, caps *Capabilities) (Values, error) {
top := map[string]interface{}{ top := map[string]interface{}{
"Release": map[string]interface{}{ "Release": map[string]interface{}{

@ -25,7 +25,8 @@ import (
kversion "k8s.io/apimachinery/pkg/version" kversion "k8s.io/apimachinery/pkg/version"
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
@ -89,16 +90,14 @@ where:
Metadata: &chart.Metadata{Name: "test"}, Metadata: &chart.Metadata{Name: "test"},
Templates: []*chart.File{}, Templates: []*chart.File{},
Values: []byte(chartValues), Values: []byte(chartValues),
Dependencies: []*chart.Chart{
{
Metadata: &chart.Metadata{Name: "where"},
Values: []byte{},
},
},
Files: []*chart.File{ Files: []*chart.File{
{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
}, },
} }
c.AddDependency(&chart.Chart{
Metadata: &chart.Metadata{Name: "where"},
Values: []byte{},
})
v := []byte(overideValues) v := []byte(overideValues)
o := ReleaseOptions{ o := ReleaseOptions{
@ -112,7 +111,7 @@ where:
KubeVersion: &kversion.Info{Major: "1"}, KubeVersion: &kversion.Info{Major: "1"},
} }
res, err := ToRenderValuesCaps(c, v, o, caps) res, err := ToRenderValues(c, v, o, caps)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -259,10 +258,8 @@ func matchValues(t *testing.T, data map[string]interface{}) {
func ttpl(tpl string, v map[string]interface{}) (string, error) { func ttpl(tpl string, v map[string]interface{}) (string, error) {
var b bytes.Buffer var b bytes.Buffer
tt := template.Must(template.New("t").Parse(tpl)) tt := template.Must(template.New("t").Parse(tpl))
if err := tt.Execute(&b, v); err != nil { err := tt.Execute(&b, v)
return "", err return b.String(), err
}
return b.String(), nil
} }
// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362
@ -293,7 +290,7 @@ pequod:
func TestCoalesceValues(t *testing.T) { func TestCoalesceValues(t *testing.T) {
tchart := "testdata/moby" tchart := "testdata/moby"
c, err := LoadDir(tchart) c, err := loader.LoadDir(tchart)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -30,9 +30,10 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/resolver" "k8s.io/helm/pkg/resolver"
@ -72,16 +73,13 @@ func (m *Manager) Build() error {
// If a lock file is found, run a build from that. Otherwise, just do // If a lock file is found, run a build from that. Otherwise, just do
// an update. // an update.
lock, err := chartutil.LoadRequirementsLock(c) lock := c.RequirementsLock
if err != nil { if lock == nil {
return m.Update() return m.Update()
} }
// A lock must accompany a requirements.yaml file. // A lock must accompany a requirements.yaml file.
req, err := chartutil.LoadRequirements(c) req := c.Requirements
if err != nil {
return errors.Wrap(err, "requirements.yaml cannot be opened")
}
if sum, err := resolver.HashReq(req); err != nil || sum != lock.Digest { if sum, err := resolver.HashReq(req); err != nil || sum != lock.Digest {
return errors.New("requirements.lock is out of sync with requirements.yaml") return errors.New("requirements.lock is out of sync with requirements.yaml")
} }
@ -119,13 +117,9 @@ func (m *Manager) Update() error {
// If no requirements file is found, we consider this a successful // If no requirements file is found, we consider this a successful
// completion. // completion.
req, err := chartutil.LoadRequirements(c) req := c.Requirements
if err != nil { if req == nil {
if err == chartutil.ErrRequirementsNotFound { return nil
fmt.Fprintf(m.Out, "No requirements found in %s/charts.\n", m.ChartPath)
return nil
}
return err
} }
// Hash requirements.yaml // Hash requirements.yaml
@ -161,8 +155,8 @@ func (m *Manager) Update() error {
} }
// If the lock file hasn't changed, don't write a new one. // If the lock file hasn't changed, don't write a new one.
oldLock, err := chartutil.LoadRequirementsLock(c) oldLock := c.RequirementsLock
if err == nil && oldLock.Digest == lock.Digest { if oldLock != nil && oldLock.Digest == lock.Digest {
return nil return nil
} }
@ -176,13 +170,13 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
} else if !fi.IsDir() { } else if !fi.IsDir() {
return nil, errors.New("only unpacked charts can be updated") return nil, errors.New("only unpacked charts can be updated")
} }
return chartutil.LoadDir(m.ChartPath) return loader.LoadDir(m.ChartPath)
} }
// resolve takes a list of requirements and translates them into an exact version to download. // resolve takes a list of requirements and translates them into an exact version to download.
// //
// This returns a lock file, which has all of the requirements normalized to a specific version. // This returns a lock file, which has all of the requirements normalized to a specific version.
func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]string, hash string) (*chartutil.RequirementsLock, error) { func (m *Manager) resolve(req *chart.Requirements, repoNames map[string]string, hash string) (*chart.RequirementsLock, error) {
res := resolver.New(m.ChartPath, m.HelmHome) res := resolver.New(m.ChartPath, m.HelmHome)
return res.Resolve(req, repoNames, hash) return res.Resolve(req, repoNames, hash)
} }
@ -191,7 +185,7 @@ func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]stri
// //
// It will delete versions of the chart that exist on disk and might cause // It will delete versions of the chart that exist on disk and might cause
// a conflict. // a conflict.
func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { func (m *Manager) downloadAll(deps []*chart.Dependency) error {
repos, err := m.loadChartRepositories() repos, err := m.loadChartRepositories()
if err != nil { if err != nil {
return err return err
@ -307,12 +301,12 @@ func (m *Manager) safeDeleteDep(name, dir string) error {
return err return err
} }
for _, fname := range files { for _, fname := range files {
ch, err := chartutil.LoadFile(fname) ch, err := loader.LoadFile(fname)
if err != nil { if err != nil {
fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err) fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err)
continue continue
} }
if ch.Metadata.Name != name { if ch.Name() != name {
// This is not the file you are looking for. // This is not the file you are looking for.
continue continue
} }
@ -325,7 +319,7 @@ func (m *Manager) safeDeleteDep(name, dir string) error {
} }
// hasAllRepos ensures that all of the referenced deps are in the local repo cache. // hasAllRepos ensures that all of the referenced deps are in the local repo cache.
func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error { func (m *Manager) hasAllRepos(deps []*chart.Dependency) error {
rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile()) rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile())
if err != nil { if err != nil {
return err return err
@ -335,25 +329,22 @@ func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error {
// Verify that all repositories referenced in the deps are actually known // Verify that all repositories referenced in the deps are actually known
// by Helm. // by Helm.
missing := []string{} missing := []string{}
Loop:
for _, dd := range deps { for _, dd := range deps {
// If repo is from local path, continue // If repo is from local path, continue
if strings.HasPrefix(dd.Repository, "file://") { if strings.HasPrefix(dd.Repository, "file://") {
continue continue
} }
found := false
if dd.Repository == "" { if dd.Repository == "" {
found = true continue
} else {
for _, repo := range repos {
if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) {
found = true
}
}
} }
if !found { for _, repo := range repos {
missing = append(missing, dd.Repository) if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) {
continue Loop
}
} }
missing = append(missing, dd.Repository)
} }
if len(missing) > 0 { if len(missing) > 0 {
return errors.Errorf("no repository definition for %s. Please add the missing repos via 'helm repo add'", strings.Join(missing, ", ")) return errors.Errorf("no repository definition for %s. Please add the missing repos via 'helm repo add'", strings.Join(missing, ", "))
@ -362,7 +353,7 @@ func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error {
} }
// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file. // getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file.
func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string, error) { func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) {
rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile()) rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile())
if err != nil { if err != nil {
return nil, err return nil, err
@ -408,24 +399,22 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string,
} }
} }
if len(missing) > 0 { if len(missing) > 0 {
if len(missing) > 0 { errorMessage := fmt.Sprintf("no repository definition for %s. Please add them via 'helm repo add'", strings.Join(missing, ", "))
errorMessage := fmt.Sprintf("no repository definition for %s. Please add them via 'helm repo add'", strings.Join(missing, ", ")) // It is common for people to try to enter "stable" as a repository instead of the actual URL.
// It is common for people to try to enter "stable" as a repository instead of the actual URL. // For this case, let's give them a suggestion.
// For this case, let's give them a suggestion. containsNonURL := false
containsNonURL := false for _, repo := range missing {
for _, repo := range missing { if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") {
if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") { containsNonURL = true
containsNonURL = true
}
} }
if containsNonURL { }
errorMessage += ` if containsNonURL {
errorMessage += `
Note that repositories must be URLs or aliases. For example, to refer to the stable Note that repositories must be URLs or aliases. For example, to refer to the stable
repository, use "https://kubernetes-charts.storage.googleapis.com/" or "@stable" instead of repository, use "https://kubernetes-charts.storage.googleapis.com/" or "@stable" instead of
"stable". Don't forget to add the repo, too ('helm repo add').` "stable". Don't forget to add the repo, too ('helm repo add').`
}
return nil, errors.New(errorMessage)
} }
return nil, errors.New(errorMessage)
} }
return reposMap, nil return reposMap, nil
} }
@ -596,7 +585,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err
} }
// writeLock writes a lockfile to disk // writeLock writes a lockfile to disk
func writeLock(chartpath string, lock *chartutil.RequirementsLock) error { func writeLock(chartpath string, lock *chart.RequirementsLock) error {
data, err := yaml.Marshal(lock) data, err := yaml.Marshal(lock)
if err != nil { if err != nil {
return err return err
@ -618,7 +607,7 @@ func tarFromLocalDir(chartpath, name, repo, version string) (string, error) {
return "", err return "", err
} }
ch, err := chartutil.LoadDir(origPath) ch, err := loader.LoadDir(origPath)
if err != nil { if err != nil {
return "", err return "", err
} }

@ -20,7 +20,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
@ -100,48 +100,48 @@ func TestGetRepoNames(t *testing.T) {
} }
tests := []struct { tests := []struct {
name string name string
req []*chartutil.Dependency req []*chart.Dependency
expect map[string]string expect map[string]string
err bool err bool
}{ }{
{ {
name: "no repo definition failure", name: "no repo definition failure",
req: []*chartutil.Dependency{ req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "http://example.com/test"}, {Name: "oedipus-rex", Repository: "http://example.com/test"},
}, },
err: true, err: true,
}, },
{ {
name: "no repo definition failure -- stable repo", name: "no repo definition failure -- stable repo",
req: []*chartutil.Dependency{ req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "stable"}, {Name: "oedipus-rex", Repository: "stable"},
}, },
err: true, err: true,
}, },
{ {
name: "no repo definition failure", name: "no repo definition failure",
req: []*chartutil.Dependency{ req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "http://example.com"}, {Name: "oedipus-rex", Repository: "http://example.com"},
}, },
expect: map[string]string{"oedipus-rex": "testing"}, expect: map[string]string{"oedipus-rex": "testing"},
}, },
{ {
name: "repo from local path", name: "repo from local path",
req: []*chartutil.Dependency{ req: []*chart.Dependency{
{Name: "local-dep", Repository: "file://./testdata/signtest"}, {Name: "local-dep", Repository: "file://./testdata/signtest"},
}, },
expect: map[string]string{"local-dep": "file://./testdata/signtest"}, expect: map[string]string{"local-dep": "file://./testdata/signtest"},
}, },
{ {
name: "repo alias (alias:)", name: "repo alias (alias:)",
req: []*chartutil.Dependency{ req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "alias:testing"}, {Name: "oedipus-rex", Repository: "alias:testing"},
}, },
expect: map[string]string{"oedipus-rex": "testing"}, expect: map[string]string{"oedipus-rex": "testing"},
}, },
{ {
name: "repo alias (@)", name: "repo alias (@)",
req: []*chartutil.Dependency{ req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "@testing"}, {Name: "oedipus-rex", Repository: "@testing"},
}, },
expect: map[string]string{"oedipus-rex": "testing"}, expect: map[string]string{"oedipus-rex": "testing"},

@ -17,7 +17,6 @@ limitations under the License.
package engine package engine
import ( import (
"bytes"
"path" "path"
"sort" "sort"
"strings" "strings"
@ -26,19 +25,19 @@ import (
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
) )
// Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. // Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates.
type Engine struct { type Engine struct {
// FuncMap contains the template functions that will be passed to each // FuncMap contains the template functions that will be passed to each
// render call. This may only be modified before the first call to Render. // render call. This may only be modified before the first call to Render.
FuncMap template.FuncMap funcMap template.FuncMap
// If strict is enabled, template rendering will fail if a template references // If strict is enabled, template rendering will fail if a template references
// a value that was not passed in. // a value that was not passed in.
Strict bool Strict bool
CurrentTemplates map[string]renderable currentTemplates map[string]renderable
} }
// New creates a new Go template Engine instance. // New creates a new Go template Engine instance.
@ -49,10 +48,7 @@ type Engine struct {
// The FuncMap sets all of the Sprig functions except for those that provide // The FuncMap sets all of the Sprig functions except for those that provide
// access to the underlying OS (env, expandenv). // access to the underlying OS (env, expandenv).
func New() *Engine { func New() *Engine {
f := FuncMap() return &Engine{funcMap: FuncMap()}
return &Engine{
FuncMap: f,
}
} }
// FuncMap returns a mapping of all of the functions that Engine has. // FuncMap returns a mapping of all of the functions that Engine has.
@ -76,11 +72,11 @@ func FuncMap() template.FuncMap {
// Add some extra functionality // Add some extra functionality
extra := template.FuncMap{ extra := template.FuncMap{
"toToml": chartutil.ToToml, "toToml": chartutil.ToTOML,
"toYaml": chartutil.ToYaml, "toYaml": chartutil.ToYAML,
"fromYaml": chartutil.FromYaml, "fromYaml": chartutil.FromYAML,
"toJson": chartutil.ToJson, "toJson": chartutil.ToJSON,
"fromJson": chartutil.FromJson, "fromJson": chartutil.FromJSON,
// This is a placeholder for the "include" function, which is // This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the // late-bound to a template. By declaring it here, we preserve the
@ -119,8 +115,8 @@ func FuncMap() template.FuncMap {
func (e *Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { func (e *Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) {
// Render the charts // Render the charts
tmap := allTemplates(chrt, values) tmap := allTemplates(chrt, values)
e.CurrentTemplates = tmap e.currentTemplates = tmap
return e.render(tmap) return e.render(chrt, tmap)
} }
// renderable is an object that can be rendered. // renderable is an object that can be rendered.
@ -138,18 +134,16 @@ type renderable struct {
// The resulting FuncMap is only valid for the passed-in template. // The resulting FuncMap is only valid for the passed-in template.
func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap { func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
// Clone the func map because we are adding context-specific functions. // Clone the func map because we are adding context-specific functions.
var funcMap template.FuncMap = map[string]interface{}{} funcMap := make(template.FuncMap)
for k, v := range e.FuncMap { for k, v := range e.funcMap {
funcMap[k] = v funcMap[k] = v
} }
// Add the 'include' function here so we can close over t. // Add the 'include' function here so we can close over t.
funcMap["include"] = func(name string, data interface{}) (string, error) { funcMap["include"] = func(name string, data interface{}) (string, error) {
buf := bytes.NewBuffer(nil) var buf strings.Builder
if err := t.ExecuteTemplate(buf, name, data); err != nil { err := t.ExecuteTemplate(&buf, name, data)
return "", err return buf.String(), err
}
return buf.String(), nil
} }
// Add the 'required' function here // Add the 'required' function here
@ -177,15 +171,15 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
basePath: basePath.(string), basePath: basePath.(string),
} }
templates := map[string]renderable{}
templateName, err := vals.PathValue("Template.Name") templateName, err := vals.PathValue("Template.Name")
if err != nil { if err != nil {
return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl) return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl)
} }
templates := make(map[string]renderable)
templates[templateName.(string)] = r templates[templateName.(string)] = r
result, err := e.render(templates) result, err := e.render(nil, templates)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl)
} }
@ -196,7 +190,7 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
} }
// render takes a map of templates/values and renders them. // render takes a map of templates/values and renders them.
func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { func (e *Engine) render(ch *chart.Chart, tpls map[string]renderable) (rendered map[string]string, err error) {
// Basically, what we do here is start with an empty parent template and then // Basically, what we do here is start with an empty parent template and then
// build up a list of templates -- one for each file. Once all of the templates // build up a list of templates -- one for each file. Once all of the templates
// have been parsed, we loop through again and execute every template. // have been parsed, we loop through again and execute every template.
@ -228,8 +222,7 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string,
for _, fname := range keys { for _, fname := range keys {
r := tpls[fname] r := tpls[fname]
t = t.New(fname).Funcs(funcMap) if _, err := t.New(fname).Funcs(funcMap).Parse(r.tpl); err != nil {
if _, err := t.Parse(r.tpl); err != nil {
return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname) return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname)
} }
files = append(files, fname) files = append(files, fname)
@ -237,17 +230,15 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string,
// Adding the engine's currentTemplates to the template context // Adding the engine's currentTemplates to the template context
// so they can be referenced in the tpl function // so they can be referenced in the tpl function
for fname, r := range e.CurrentTemplates { for fname, r := range e.currentTemplates {
if t.Lookup(fname) == nil { if t.Lookup(fname) == nil {
t = t.New(fname).Funcs(funcMap) if _, err := t.New(fname).Funcs(funcMap).Parse(r.tpl); err != nil {
if _, err := t.Parse(r.tpl); err != nil {
return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname) return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname)
} }
} }
} }
rendered = make(map[string]string, len(files)) rendered = make(map[string]string, len(files))
var buf bytes.Buffer
for _, file := range files { for _, file := range files {
// Don't render partials. We don't care out the direct output of partials. // Don't render partials. We don't care out the direct output of partials.
// They are only included from other templates. // They are only included from other templates.
@ -256,7 +247,8 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string,
} }
// At render time, add information about the template that is being rendered. // At render time, add information about the template that is being rendered.
vals := tpls[file].vals vals := tpls[file].vals
vals["Template"] = map[string]interface{}{"Name": file, "BasePath": tpls[file].basePath} vals["Template"] = chartutil.Values{"Name": file, "BasePath": tpls[file].basePath}
var buf strings.Builder
if err := t.ExecuteTemplate(&buf, file, vals); err != nil { if err := t.ExecuteTemplate(&buf, file, vals); err != nil {
return map[string]string{}, errors.Wrapf(err, "render error in %q", file) return map[string]string{}, errors.Wrapf(err, "render error in %q", file)
} }
@ -264,8 +256,14 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string,
// Work around the issue where Go will emit "<no value>" even if Options(missing=zero) // Work around the issue where Go will emit "<no value>" even if Options(missing=zero)
// is set. Since missing=error will never get here, we do not need to handle // is set. Since missing=error will never get here, we do not need to handle
// the Strict case. // the Strict case.
rendered[file] = strings.Replace(buf.String(), "<no value>", "", -1) f := &chart.File{
buf.Reset() Name: strings.Replace(file, "/templates", "/manifests", -1),
Data: []byte(strings.Replace(buf.String(), "<no value>", "", -1)),
}
rendered[file] = string(f.Data)
if ch != nil {
ch.Files = append(ch.Files, f)
}
} }
return rendered, nil return rendered, nil
@ -299,8 +297,8 @@ func (p byPathLen) Less(i, j int) bool {
// //
// As it goes, it also prepares the values in a scope-sensitive manner. // As it goes, it also prepares the values in a scope-sensitive manner.
func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable {
templates := map[string]renderable{} templates := make(map[string]renderable)
recAllTpls(c, templates, vals, true, "") recAllTpls(c, templates, vals)
return templates return templates
} }
@ -308,44 +306,32 @@ func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable {
// //
// As it recurses, it also sets the values to be appropriate for the template // As it recurses, it also sets the values to be appropriate for the template
// scope. // scope.
func recAllTpls(c *chart.Chart, templates map[string]renderable, parentVals chartutil.Values, top bool, parentID string) { func recAllTpls(c *chart.Chart, templates map[string]renderable, parentVals chartutil.Values) {
// This should never evaluate to a nil map. That will cause problems when // This should never evaluate to a nil map. That will cause problems when
// values are appended later. // values are appended later.
cvals := chartutil.Values{} cvals := make(chartutil.Values)
if top { if c.IsRoot() {
// If this is the top of the rendering tree, assume that parentVals
// is already resolved to the authoritative values.
cvals = parentVals cvals = parentVals
} else if c.Metadata != nil && c.Metadata.Name != "" { } else if c.Name() != "" {
// If there is a {{.Values.ThisChart}} in the parent metadata,
// copy that into the {{.Values}} for this template.
newVals := chartutil.Values{}
if vs, err := parentVals.Table("Values"); err == nil {
if tmp, err := vs.Table(c.Metadata.Name); err == nil {
newVals = tmp
}
}
cvals = map[string]interface{}{ cvals = map[string]interface{}{
"Values": newVals, "Values": make(chartutil.Values),
"Release": parentVals["Release"], "Release": parentVals["Release"],
"Chart": c.Metadata, "Chart": c.Metadata,
"Files": chartutil.NewFiles(c.Files), "Files": chartutil.NewFiles(c.Files),
"Capabilities": parentVals["Capabilities"], "Capabilities": parentVals["Capabilities"],
} }
// If there is a {{.Values.ThisChart}} in the parent metadata,
// copy that into the {{.Values}} for this template.
if vs, err := parentVals.Table("Values." + c.Name()); err == nil {
cvals["Values"] = vs
}
} }
newParentID := c.Metadata.Name for _, child := range c.Dependencies() {
if parentID != "" { recAllTpls(child, templates, cvals)
// We artificially reconstruct the chart path to child templates. This
// creates a namespaced filename that can be used to track down the source
// of a particular template declaration.
newParentID = path.Join(parentID, "charts", newParentID)
} }
for _, child := range c.Dependencies { newParentID := c.ChartFullPath()
recAllTpls(child, templates, cvals, false, newParentID)
}
for _, t := range c.Templates { for _, t := range c.Templates {
templates[path.Join(newParentID, t.Name)] = renderable{ templates[path.Join(newParentID, t.Name)] = renderable{
tpl: string(t.Data), tpl: string(t.Data),

@ -21,8 +21,8 @@ import (
"sync" "sync"
"testing" "testing"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/chart"
) )
func TestSortTemplates(t *testing.T) { func TestSortTemplates(t *testing.T) {
@ -62,7 +62,7 @@ func TestEngine(t *testing.T) {
// Forbidden because they allow access to the host OS. // Forbidden because they allow access to the host OS.
forbidden := []string{"env", "expandenv"} forbidden := []string{"env", "expandenv"}
for _, f := range forbidden { for _, f := range forbidden {
if _, ok := e.FuncMap[f]; ok { if _, ok := e.funcMap[f]; ok {
t.Errorf("Forbidden function %s exists in FuncMap.", f) t.Errorf("Forbidden function %s exists in FuncMap.", f)
} }
} }
@ -149,7 +149,7 @@ func TestRenderInternals(t *testing.T) {
"three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals},
} }
out, err := e.render(tpls) out, err := e.render(nil, tpls)
if err != nil { if err != nil {
t.Fatalf("Failed template rendering: %s", err) t.Fatalf("Failed template rendering: %s", err)
} }
@ -182,7 +182,7 @@ func TestParallelRenderInternals(t *testing.T) {
tt := fmt.Sprintf("expect-%d", i) tt := fmt.Sprintf("expect-%d", i)
v := chartutil.Values{"val": tt} v := chartutil.Values{"val": tt}
tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}} tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}}
out, err := e.render(tpls) out, err := e.render(nil, tpls)
if err != nil { if err != nil {
t.Errorf("Failed to render %s: %s", tt, err) t.Errorf("Failed to render %s: %s", tt, err)
} }
@ -202,22 +202,23 @@ func TestAllTemplates(t *testing.T) {
{Name: "templates/foo", Data: []byte("foo")}, {Name: "templates/foo", Data: []byte("foo")},
{Name: "templates/bar", Data: []byte("bar")}, {Name: "templates/bar", Data: []byte("bar")},
}, },
Dependencies: []*chart.Chart{ }
{ dep1 := &chart.Chart{
Metadata: &chart.Metadata{Name: "laboratory mice"}, Metadata: &chart.Metadata{Name: "laboratory mice"},
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/pinky", Data: []byte("pinky")}, {Name: "templates/pinky", Data: []byte("pinky")},
{Name: "templates/brain", Data: []byte("brain")}, {Name: "templates/brain", Data: []byte("brain")},
}, },
Dependencies: []*chart.Chart{{ }
Metadata: &chart.Metadata{Name: "same thing we do every night"}, ch1.AddDependency(dep1)
Templates: []*chart.File{
{Name: "templates/innermost", Data: []byte("innermost")}, dep2 := &chart.Chart{
}}, Metadata: &chart.Metadata{Name: "same thing we do every night"},
}, Templates: []*chart.File{
}, {Name: "templates/innermost", Data: []byte("innermost")},
}, },
} }
dep1.AddDependency(dep2)
var v chartutil.Values var v chartutil.Values
tpls := allTemplates(ch1, v) tpls := allTemplates(ch1, v)
@ -235,18 +236,15 @@ func TestRenderDependency(t *testing.T) {
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/outer", Data: []byte(toptpl)}, {Name: "templates/outer", Data: []byte(toptpl)},
}, },
Dependencies: []*chart.Chart{
{
Metadata: &chart.Metadata{Name: "innerchart"},
Templates: []*chart.File{
{Name: "templates/inner", Data: []byte(deptpl)},
},
},
},
} }
ch.AddDependency(&chart.Chart{
Metadata: &chart.Metadata{Name: "innerchart"},
Templates: []*chart.File{
{Name: "templates/inner", Data: []byte(deptpl)},
},
})
out, err := e.Render(ch, map[string]interface{}{}) out, err := e.Render(ch, map[string]interface{}{})
if err != nil { if err != nil {
t.Fatalf("failed to render chart: %s", err) t.Fatalf("failed to render chart: %s", err)
} }
@ -285,9 +283,9 @@ func TestRenderNestedValues(t *testing.T) {
Templates: []*chart.File{ Templates: []*chart.File{
{Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)},
}, },
Values: []byte(`who: "Robert"`), Values: []byte(`who: "Robert"`),
Dependencies: []*chart.Chart{deepest},
} }
inner.AddDependency(deepest)
outer := &chart.Chart{ outer := &chart.Chart{
Metadata: &chart.Metadata{Name: "top"}, Metadata: &chart.Metadata{Name: "top"},
@ -299,8 +297,8 @@ what: stinkweed
who: me who: me
herrick: herrick:
who: time`), who: time`),
Dependencies: []*chart.Chart{inner},
} }
outer.AddDependency(inner)
injValues := []byte(` injValues := []byte(`
what: rosebuds what: rosebuds
@ -358,8 +356,6 @@ func TestRenderBuiltinValues(t *testing.T) {
{Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
{Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)}, {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{},
Files: []*chart.File{ Files: []*chart.File{
{Name: "author", Data: []byte("Virgil")}, {Name: "author", Data: []byte("Virgil")},
{Name: "book/title.txt", Data: []byte("Aeneid")}, {Name: "book/title.txt", Data: []byte("Aeneid")},
@ -371,9 +367,8 @@ func TestRenderBuiltinValues(t *testing.T) {
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{inner},
} }
outer.AddDependency(inner)
inject := chartutil.Values{ inject := chartutil.Values{
"Values": "", "Values": "",
@ -403,15 +398,13 @@ func TestRenderBuiltinValues(t *testing.T) {
} }
func TestAlterFuncMap(t *testing.T) { func TestAlterFuncMap_include(t *testing.T) {
c := &chart.Chart{ c := &chart.Chart{
Metadata: &chart.Metadata{Name: "conrad"}, Metadata: &chart.Metadata{Name: "conrad"},
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)},
{Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{},
} }
v := chartutil.Values{ v := chartutil.Values{
@ -431,127 +424,127 @@ func TestAlterFuncMap(t *testing.T) {
if got := out["conrad/templates/quote"]; got != expect { if got := out["conrad/templates/quote"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expect, got, out) t.Errorf("Expected %q, got %q (%v)", expect, got, out)
} }
}
reqChart := &chart.Chart{ func TestAlterFuncMap_require(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "conan"}, Metadata: &chart.Metadata{Name: "conan"},
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)}, {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)},
{Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)}, {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{},
} }
reqValues := chartutil.Values{ v := chartutil.Values{
"Values": chartutil.Values{ "Values": chartutil.Values{
"who": "us", "who": "us",
"bases": 2, "bases": 2,
}, },
"Chart": reqChart.Metadata, "Chart": c.Metadata,
"Release": chartutil.Values{ "Release": chartutil.Values{
"Name": "That 90s meme", "Name": "That 90s meme",
}, },
} }
outReq, err := New().Render(reqChart, reqValues) out, err := New().Render(c, v)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectStr := "All your base are belong to us" expectStr := "All your base are belong to us"
if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr { if gotStr := out["conan/templates/quote"]; gotStr != expectStr {
t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq) t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
} }
expectNum := "All 2 of them!" expectNum := "All 2 of them!"
if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum { if gotNum := out["conan/templates/bases"]; gotNum != expectNum {
t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq) t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out)
} }
}
tplChart := &chart.Chart{ func TestAlterFuncMap_tpl(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "TplFunction"}, Metadata: &chart.Metadata{Name: "TplFunction"},
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{},
} }
tplValues := chartutil.Values{ v := chartutil.Values{
"Values": chartutil.Values{ "Values": chartutil.Values{
"value": "myvalue", "value": "myvalue",
}, },
"Chart": tplChart.Metadata, "Chart": c.Metadata,
"Release": chartutil.Values{ "Release": chartutil.Values{
"Name": "TestRelease", "Name": "TestRelease",
}, },
} }
outTpl, err := New().Render(tplChart, tplValues) out, err := New().Render(c, v)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectTplStr := "Evaluate tpl Value: myvalue" expect := "Evaluate tpl Value: myvalue"
if gotStrTpl := outTpl["TplFunction/templates/base"]; gotStrTpl != expectTplStr { if got := out["TplFunction/templates/base"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expectTplStr, gotStrTpl, outTpl) t.Errorf("Expected %q, got %q (%v)", expect, got, out)
} }
}
tplChartWithFunction := &chart.Chart{ func TestAlterFuncMap_tplfunc(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "TplFunction"}, Metadata: &chart.Metadata{Name: "TplFunction"},
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{},
} }
tplValuesWithFunction := chartutil.Values{ v := chartutil.Values{
"Values": chartutil.Values{ "Values": chartutil.Values{
"value": "myvalue", "value": "myvalue",
}, },
"Chart": tplChartWithFunction.Metadata, "Chart": c.Metadata,
"Release": chartutil.Values{ "Release": chartutil.Values{
"Name": "TestRelease", "Name": "TestRelease",
}, },
} }
outTplWithFunction, err := New().Render(tplChartWithFunction, tplValuesWithFunction) out, err := New().Render(c, v)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectTplStrWithFunction := "Evaluate tpl Value: \"myvalue\"" expect := "Evaluate tpl Value: \"myvalue\""
if gotStrTplWithFunction := outTplWithFunction["TplFunction/templates/base"]; gotStrTplWithFunction != expectTplStrWithFunction { if got := out["TplFunction/templates/base"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expectTplStrWithFunction, gotStrTplWithFunction, outTplWithFunction) t.Errorf("Expected %q, got %q (%v)", expect, got, out)
} }
}
tplChartWithInclude := &chart.Chart{ func TestAlterFuncMap_tplinclude(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "TplFunction"}, Metadata: &chart.Metadata{Name: "TplFunction"},
Templates: []*chart.File{ Templates: []*chart.File{
{Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)},
{Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)},
}, },
Values: []byte{},
Dependencies: []*chart.Chart{},
} }
tplValueWithInclude := chartutil.Values{ v := chartutil.Values{
"Values": chartutil.Values{ "Values": chartutil.Values{
"value": "myvalue", "value": "myvalue",
}, },
"Chart": tplChartWithInclude.Metadata, "Chart": c.Metadata,
"Release": chartutil.Values{ "Release": chartutil.Values{
"Name": "TestRelease", "Name": "TestRelease",
}, },
} }
outTplWithInclude, err := New().Render(tplChartWithInclude, tplValueWithInclude) out, err := New().Render(c, v)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectedTplStrWithInclude := "\"TplFunction/templates/base\"" expect := "\"TplFunction/templates/base\""
if gotStrTplWithInclude := outTplWithInclude["TplFunction/templates/base"]; gotStrTplWithInclude != expectedTplStrWithInclude { if got := out["TplFunction/templates/base"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expectedTplStrWithInclude, gotStrTplWithInclude, outTplWithInclude) t.Errorf("Expected %q, got %q (%v)", expect, got, out)
} }
} }

@ -1,32 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chart
// Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies).
type Chart struct {
// Metadata is the contents of the Chartfile.
Metadata *Metadata `json:"metadata,omitempty"`
// Templates for this chart.
Templates []*File `json:"templates,omitempty"`
// Dependencies are the charts that this chart depends on.
Dependencies []*Chart `json:"dependencies,omitempty"`
// Values are default config for this template.
Values []byte `json:"values,omitempty"`
// Files are miscellaneous files in a chart archive,
// e.g. README, LICENSE, etc.
Files []*File `json:"files,omitempty"`
}

@ -15,7 +15,7 @@ limitations under the License.
package release package release
import "k8s.io/helm/pkg/hapi/chart" import "k8s.io/helm/pkg/chart"
// Release describes a deployment of a chart, together with the chart // Release describes a deployment of a chart, together with the chart
// and the variables used to deploy that chart. // and the variables used to deploy that chart.

@ -16,7 +16,7 @@ limitations under the License.
package hapi package hapi
import ( import (
"k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
) )

@ -17,13 +17,13 @@ limitations under the License.
package helm // import "k8s.io/helm/pkg/helm" package helm // import "k8s.io/helm/pkg/helm"
import ( import (
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi" "k8s.io/helm/pkg/hapi"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/tiller" "k8s.io/helm/pkg/tiller"
"k8s.io/helm/pkg/tiller/environment"
) )
// Client manages client side of the Helm-Tiller protocol. // Client manages client side of the Helm-Tiller protocol.
@ -39,8 +39,7 @@ func NewClient(opts ...Option) *Client {
} }
func (c *Client) init() *Client { func (c *Client) init() *Client {
env := environment.New() c.tiller = tiller.NewReleaseServer(c.opts.discovery, c.opts.kubeClient)
c.tiller = tiller.NewReleaseServer(env, c.opts.discovery, c.opts.kubeClient)
c.tiller.Releases = storage.Init(c.opts.driver) c.tiller.Releases = storage.Init(c.opts.driver)
return c return c
} }
@ -69,7 +68,7 @@ func (c *Client) ListReleases(opts ...ReleaseListOption) ([]*release.Release, er
// InstallRelease loads a chart from chstr, installs it, and returns the release response. // InstallRelease loads a chart from chstr, installs it, and returns the release response.
func (c *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*release.Release, error) { func (c *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*release.Release, error) {
// load the chart to install // load the chart to install
chart, err := chartutil.Load(chstr) chart, err := loader.Load(chstr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -136,7 +135,7 @@ func (c *Client) UninstallRelease(rlsName string, opts ...UninstallOption) (*hap
// UpdateRelease loads a chart from chstr and updates a release to a new/different chart. // UpdateRelease loads a chart from chstr and updates a release to a new/different chart.
func (c *Client) UpdateRelease(rlsName, chstr string, opts ...UpdateOption) (*release.Release, error) { func (c *Client) UpdateRelease(rlsName, chstr string, opts ...UpdateOption) (*release.Release, error) {
// load the chart to update // load the chart to update
chart, err := chartutil.Load(chstr) chart, err := loader.Load(chstr)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -23,8 +23,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/hapi" "k8s.io/helm/pkg/hapi"
"k8s.io/helm/pkg/hapi/chart"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hapi/release"
) )

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save