diff --git a/cmd/helm/create.go b/cmd/helm/create.go index 6e63fd305..5569a8e1c 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -24,8 +24,8 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" ) const createDesc = ` @@ -80,7 +80,7 @@ func (o *createOptions) run(out io.Writer) error { Description: "A Helm chart for Kubernetes", Version: "0.1.0", AppVersion: "1.0", - APIVersion: chartutil.APIVersionv1, + APIVersion: chart.APIVersionv1, } if o.starter != "" { diff --git a/cmd/helm/create_test.go b/cmd/helm/create_test.go index 5ec69f678..47fc88520 100644 --- a/cmd/helm/create_test.go +++ b/cmd/helm/create_test.go @@ -23,8 +23,9 @@ import ( "path/filepath" "testing" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" ) func TestCreateCmd(t *testing.T) { @@ -46,15 +47,15 @@ func TestCreateCmd(t *testing.T) { t.Fatalf("chart is not directory") } - c, err := chartutil.LoadDir(cname) + c, err := loader.LoadDir(cname) if err != nil { t.Fatal(err) } - if c.Metadata.Name != cname { - t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name) + if c.Name() != cname { + 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) } } @@ -97,15 +98,15 @@ func TestCreateStarterCmd(t *testing.T) { t.Fatalf("chart is not directory") } - c, err := chartutil.LoadDir(cname) + c, err := loader.LoadDir(cname) if err != nil { t.Fatal(err) } - if c.Metadata.Name != cname { - t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name) + if c.Name() != cname { + 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) } diff --git a/cmd/helm/dependency.go b/cmd/helm/dependency.go index e31c26000..d8eb41e77 100644 --- a/cmd/helm/dependency.go +++ b/cmd/helm/dependency.go @@ -26,7 +26,8 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" - "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" ) const dependencyDesc = ` @@ -130,27 +131,23 @@ func newDependencyListCmd(out io.Writer) *cobra.Command { } func (o *dependencyLisOptions) run(out io.Writer) error { - c, err := chartutil.Load(o.chartpath) + c, err := loader.Load(o.chartpath) if err != nil { return err } - r, err := chartutil.LoadRequirements(c) - if err != nil { - if err == chartutil.ErrRequirementsNotFound { - fmt.Fprintf(out, "WARNING: no requirements at %s/charts\n", o.chartpath) - return nil - } - return err + if c.Requirements == nil { + fmt.Fprintf(out, "WARNING: no requirements at %s/charts\n", o.chartpath) + return nil } - o.printRequirements(out, r) + o.printRequirements(out, c.Requirements) fmt.Fprintln(out) - o.printMissing(out, r) + o.printMissing(out, c.Requirements) 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, "*") archives, err := filepath.Glob(filepath.Join(o.chartpath, "charts", filename)) if err != nil { @@ -160,11 +157,11 @@ func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) strin } else if len(archives) == 1 { archive := archives[0] if _, err := os.Stat(archive); err == nil { - c, err := chartutil.Load(archive) + c, err := loader.Load(archive) if err != nil { return "corrupt" } - if c.Metadata.Name != dep.Name { + if c.Name() != dep.Name { return "misnamed" } @@ -195,12 +192,12 @@ func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) strin return "mispackaged" } - c, err := chartutil.Load(folder) + c, err := loader.Load(folder) if err != nil { return "corrupt" } - if c.Metadata.Name != dep.Name { + if c.Name() != dep.Name { return "misnamed" } @@ -225,7 +222,7 @@ func (o *dependencyLisOptions) dependencyStatus(dep *chartutil.Dependency) strin } // 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.MaxColWidth = 80 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. -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/*") files, err := filepath.Glob(folder) if err != nil { @@ -253,14 +250,14 @@ func (o *dependencyLisOptions) printMissing(out io.Writer, reqs *chartutil.Requi if !fi.IsDir() && filepath.Ext(f) != ".tgz" { continue } - c, err := chartutil.Load(f) + c, err := loader.Load(f) if err != nil { fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f) continue } found := false for _, d := range reqs.Dependencies { - if d.Name == c.Metadata.Name { + if d.Name == c.Name() { found = true break } diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go index 212106af4..7401b7579 100644 --- a/cmd/helm/dependency_update_test.go +++ b/cmd/helm/dependency_update_test.go @@ -26,9 +26,8 @@ import ( "github.com/ghodss/yaml" + "k8s.io/helm/pkg/chart" "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/repo" "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, // old dependencies are cleansed and new dependencies are added. - reqfile := &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + reqfile := &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "reqtest", Version: "0.1.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) o := &dependencyUpdateOptions{} - o.helmhome = helmpath.Home(hh) + o.helmhome = hh o.chartpath = hh.Path(chartname) if err := o.run(out); err != nil { @@ -223,8 +222,8 @@ func createTestingChart(dest, name, baseURL string) error { if err != nil { return err } - req := &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req := &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "reqtest", 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) } -func writeRequirements(dir string, req *chartutil.Requirements) error { +func writeRequirements(dir string, req *chart.Requirements) error { data, err := yaml.Marshal(req) if err != nil { return err diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 1139d9648..65eb75590 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -27,7 +27,7 @@ import ( "github.com/spf13/cobra" "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/helm" ) @@ -167,5 +167,5 @@ func formatChartname(c *chart.Chart) string { // know how: https://github.com/kubernetes/helm/issues/1347 return "MISSING" } - return fmt.Sprintf("%s-%s", c.Metadata.Name, c.Metadata.Version) + return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version) } diff --git a/cmd/helm/inspect.go b/cmd/helm/inspect.go index 4f8327898..986273158 100644 --- a/cmd/helm/inspect.go +++ b/cmd/helm/inspect.go @@ -25,8 +25,8 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" - "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" ) const inspectDesc = ` @@ -146,7 +146,7 @@ func newInspectCmd(out io.Writer) *cobra.Command { } func (i *inspectOptions) run(out io.Writer) error { - chrt, err := chartutil.Load(i.chartpath) + chrt, err := loader.Load(i.chartpath) if err != nil { return err } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index e7910d57e..c953a2e46 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -28,10 +28,10 @@ import ( "github.com/spf13/cobra" "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/getter" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" "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 - chartRequested, err := chartutil.Load(o.chartPath) + chartRequested, err := loader.Load(o.chartPath) if err != nil { 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. // As of Helm 2.4.0, this is treated as a stopping condition: // 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( @@ -272,7 +270,6 @@ func (o *installOptions) printRelease(out io.Writer, rel *release.Release) { if rel == nil { return } - // TODO: Switch to text/template like everything else. fmt.Fprintf(out, "NAME: %s\n", rel.Name) if settings.Debug { printRelease(out, rel) @@ -286,27 +283,20 @@ func generateName(nameTemplate string) (string, error) { } var b bytes.Buffer err = t.Execute(&b, nil) - if err != nil { - return "", err - } - return b.String(), nil + return b.String(), err } -func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { - missing := []string{} +func checkDependencies(ch *chart.Chart, reqs *chart.Requirements) error { + var missing []string - deps := ch.Dependencies +OUTER: for _, r := range reqs.Dependencies { - found := false - for _, d := range deps { - if d.Metadata.Name == r.Name { - found = true - break + for _, d := range ch.Dependencies() { + if d.Name() == r.Name { + continue OUTER } } - if !found { - missing = append(missing, r.Name) - } + missing = append(missing, r.Name) } if len(missing) > 0 { diff --git a/cmd/helm/package.go b/cmd/helm/package.go index a41ee4f12..1e99b1eae 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -30,10 +30,11 @@ import ( "github.com/spf13/cobra" "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/downloader" "k8s.io/helm/pkg/getter" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/helm/helmpath" "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 { return err } @@ -161,18 +162,14 @@ func (o *packageOptions) run(out io.Writer) error { debug("Setting appVersion to %s", o.appVersion) } - if filepath.Base(path) != ch.Metadata.Name { - return errors.Errorf("directory name (%s) and Chart.yaml name (%s) must match", 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.Name()) } - if reqs, err := chartutil.LoadRequirements(ch); err == nil { + if reqs := ch.Requirements; reqs != nil { if err := checkDependencies(ch, reqs); err != nil { return err } - } else { - if err != chartutil.ErrRequirementsNotFound { - return err - } } var dest string diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index d7f0e0f9d..b2a11eb98 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -26,8 +26,9 @@ import ( "github.com/spf13/cobra" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/helm/helmpath" ) @@ -206,7 +207,7 @@ func TestSetAppVersion(t *testing.T) { tmp := testTempDir(t) hh := testHelmHome(t) - settings.Home = helmpath.Home(hh) + settings.Home = hh c := newPackageCmd(&bytes.Buffer{}) flags := map[string]string{ @@ -224,7 +225,7 @@ func TestSetAppVersion(t *testing.T) { } else if fi.Size() == 0 { t.Errorf("file %q has zero bytes.", chartPath) } - ch, err := chartutil.Load(chartPath) + ch, err := loader.Load(chartPath) if err != nil { 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) { - chart, err := chartutil.Load(chartPath) + chart, err := loader.Load(chartPath) if err != nil { return nil, err } diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index b84cd7a2d..b040bdc6e 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -45,7 +45,7 @@ func TestUpdateCmd(t *testing.T) { } o := &repoUpdateOptions{ update: updater, - home: helmpath.Home(hh), + home: hh, } if err := o.run(out); err != nil { t.Fatal(err) diff --git a/cmd/helm/search/search_test.go b/cmd/helm/search/search_test.go index 19568d9ca..92bf6c97d 100644 --- a/cmd/helm/search/search_test.go +++ b/cmd/helm/search/search_test.go @@ -20,7 +20,7 @@ import ( "strings" "testing" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/repo" ) diff --git a/cmd/helm/template.go b/cmd/helm/template.go index a69cd6c6f..08682f2ff 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -31,12 +31,12 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/hapi/release" util "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/tiller" - tversion "k8s.io/helm/pkg/version" ) 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 - c, err := chartutil.Load(o.chartPath) + c, err := loader.Load(o.chartPath) if err != nil { return err } - if req, err := chartutil.LoadRequirements(c); err == nil { + if req := c.Requirements; req != nil { if err := checkDependencies(c, req); err != nil { return err } - } else if err != chartutil.ErrRequirementsNotFound { - return errors.Wrap(err, "cannot load requirements") } options := chartutil.ReleaseOptions{ Name: o.releaseName, @@ -178,22 +176,18 @@ func (o *templateOptions) run(out io.Writer) error { // Set up engine. renderer := engine.New() - caps := &chartutil.Capabilities{ - APIVersions: chartutil.DefaultVersionSet, - KubeVersion: chartutil.DefaultKubeVersion, - HelmVersion: tversion.GetBuildInfo(), - } - // kubernetes version kv, err := semver.NewVersion(o.kubeVersion) if err != nil { return errors.Wrap(err, "could not parse a kubernetes version") } + + caps := chartutil.DefaultCapabilities caps.KubeVersion.Major = fmt.Sprint(kv.Major()) caps.KubeVersion.Minor = fmt.Sprint(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 { return err } diff --git a/cmd/helm/testdata/output/upgrade-with-bad-dependencies.txt b/cmd/helm/testdata/output/upgrade-with-bad-dependencies.txt index c77fba18b..a50915b9b 100644 --- a/cmd/helm/testdata/output/upgrade-with-bad-dependencies.txt +++ b/cmd/helm/testdata/output/upgrade-with-bad-dependencies.txt @@ -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 diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 83e2bb24d..c7db6fb7e 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -25,7 +25,7 @@ import ( "github.com/spf13/cobra" "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/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 - if ch, err := chartutil.Load(chartPath); err == nil { - if req, err := chartutil.LoadRequirements(ch); err == nil { - if err := checkDependencies(ch, req); err != nil { - return err - } - } else if err != chartutil.ErrRequirementsNotFound { - return errors.Wrap(err, "cannot load requirements") - } - } else { + ch, err := loader.Load(chartPath) + if err != nil { return err } + if req := ch.Requirements; req != nil { + if err := checkDependencies(ch, req); err != nil { + return err + } + } resp, err := o.client.UpdateRelease( o.release, diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index c75b50b1a..904085725 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -19,8 +19,9 @@ package main import ( "testing" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/helm" ) @@ -36,7 +37,7 @@ func TestUpgradeCmd(t *testing.T) { if err != nil { t.Fatalf("Error creating chart for upgrade: %v", err) } - ch, err := chartutil.Load(chartPath) + ch, err := loader.Load(chartPath) if err != nil { t.Fatalf("Error loading chart: %v", err) } @@ -56,7 +57,7 @@ func TestUpgradeCmd(t *testing.T) { if err != nil { t.Fatalf("Error creating chart: %v", err) } - ch, err = chartutil.Load(chartPath) + ch, err = loader.Load(chartPath) if err != nil { t.Fatalf("Error loading updated chart: %v", err) } @@ -73,7 +74,7 @@ func TestUpgradeCmd(t *testing.T) { t.Fatalf("Error creating chart: %v", err) } var ch2 *chart.Chart - ch2, err = chartutil.Load(chartPath) + ch2, err = loader.Load(chartPath) if err != nil { t.Fatalf("Error loading updated chart: %v", err) } diff --git a/docs/examples/nginx/charts/alpine/Chart.yaml b/docs/examples/nginx/charts/alpine/Chart.yaml new file mode 100644 index 000000000..f4b660d4f --- /dev/null +++ b/docs/examples/nginx/charts/alpine/Chart.yaml @@ -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 diff --git a/docs/examples/nginx/charts/alpine/README.md b/docs/examples/nginx/charts/alpine/README.md new file mode 100644 index 000000000..3e354724c --- /dev/null +++ b/docs/examples/nginx/charts/alpine/README.md @@ -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`. diff --git a/docs/examples/nginx/charts/alpine/templates/_helpers.tpl b/docs/examples/nginx/charts/alpine/templates/_helpers.tpl new file mode 100644 index 000000000..3e9c25bed --- /dev/null +++ b/docs/examples/nginx/charts/alpine/templates/_helpers.tpl @@ -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 -}} diff --git a/docs/examples/nginx/charts/alpine/templates/alpine-pod.yaml b/docs/examples/nginx/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..da9caef78 --- /dev/null +++ b/docs/examples/nginx/charts/alpine/templates/alpine-pod.yaml @@ -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"] diff --git a/docs/examples/nginx/charts/alpine/values.yaml b/docs/examples/nginx/charts/alpine/values.yaml new file mode 100644 index 000000000..afe8cc6c0 --- /dev/null +++ b/docs/examples/nginx/charts/alpine/values.yaml @@ -0,0 +1,6 @@ +image: + repository: alpine + tag: 3.3 + pullPolicy: IfNotPresent + +restartPolicy: Never diff --git a/docs/examples/nginx/templates/NOTES.txt b/docs/examples/nginx/templates/NOTES.txt new file mode 100644 index 000000000..4bdf443f6 --- /dev/null +++ b/docs/examples/nginx/templates/NOTES.txt @@ -0,0 +1 @@ +Sample notes for {{ .Chart.Name }} \ No newline at end of file diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go new file mode 100644 index 000000000..b51bb5b90 --- /dev/null +++ b/pkg/chart/chart.go @@ -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() +} diff --git a/pkg/chartutil/transform.go b/pkg/chart/chartfile.go similarity index 56% rename from pkg/chartutil/transform.go rename to pkg/chart/chartfile.go index f360e4fad..b669b781b 100644 --- a/pkg/chartutil/transform.go +++ b/pkg/chart/chartfile.go @@ -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"); 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 +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, @@ -14,12 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil - -import "strings" +package chart -// Transform performs a string replacement of the specified source for -// a given key with the replacement string -func Transform(src, key, replacement string) []byte { - return []byte(strings.Replace(src, key, replacement, -1)) -} +// APIVersionv1 is the API version number for version 1. +const APIVersionv1 = "v1" diff --git a/pkg/hapi/chart/file.go b/pkg/chart/file.go similarity index 92% rename from pkg/hapi/chart/file.go rename to pkg/chart/file.go index 90edd59f1..53ce89d3f 100644 --- a/pkg/hapi/chart/file.go +++ b/pkg/chart/file.go @@ -21,7 +21,7 @@ package chart // base directory. type File struct { // Name is the path-like name of the template. - Name string `json:"name,omitempty"` + Name string // Data is the template as byte data. - Data []byte `json:"data,omitempty"` + Data []byte } diff --git a/pkg/chart/loader/archive.go b/pkg/chart/loader/archive.go new file mode 100644 index 000000000..dbdab98f9 --- /dev/null +++ b/pkg/chart/loader/archive.go @@ -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) +} diff --git a/pkg/chart/loader/directory.go b/pkg/chart/loader/directory.go new file mode 100644 index 000000000..f51620cfb --- /dev/null +++ b/pkg/chart/loader/directory.go @@ -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) +} diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go new file mode 100644 index 000000000..bbc0cca63 --- /dev/null +++ b/pkg/chart/loader/load.go @@ -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 +} diff --git a/pkg/chartutil/load_test.go b/pkg/chart/loader/load_test.go similarity index 63% rename from pkg/chartutil/load_test.go rename to pkg/chart/loader/load_test.go index 36dc37185..aca222780 100644 --- a/pkg/chartutil/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -14,27 +14,35 @@ See the License for the specific language governing permissions and limitations under the License. */ -package chartutil +package loader import ( - "path" "testing" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" ) 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 { t.Fatalf("Failed to load testdata: %s", err) } verifyFrobnitz(t, c) verifyChart(t, c) verifyRequirements(t, c) + verifyRequirementsLock(t, c) } 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 { t.Fatalf("Failed to load testdata: %s", err) } @@ -46,7 +54,7 @@ func TestLoadFile(t *testing.T) { func TestLoadFiles(t *testing.T) { goodFiles := []*BufferedFile{ { - Name: ChartfileName, + Name: "Chart.yaml", Data: []byte(`apiVersion: v1 name: frobnitz description: This is a frobnitz. @@ -67,16 +75,16 @@ icon: https://example.com/64x64.png `), }, { - Name: ValuesfileName, - Data: []byte(defaultValues), + Name: "values.yaml", + Data: []byte("some values"), }, { - Name: path.Join("templates", DeploymentName), - Data: []byte(defaultDeployment), + Name: "templates/deployment.yaml", + Data: []byte("some deployment"), }, { - Name: path.Join("templates", ServiceName), - Data: []byte(defaultService), + Name: "templates/service.yaml", + 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) } - if c.Metadata.Name != "frobnitz" { - t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Metadata.Name) + if c.Name() != "frobnitz" { + 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") } @@ -119,15 +127,16 @@ func TestLoadFileBackslash(t *testing.T) { } 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.Logf("Verifying chart %s", c.Metadata.Name) + t.Logf("Verifying chart %s", c.Name()) if len(c.Templates) != 1 { t.Errorf("Expected 1 template, got %d", len(c.Templates)) } - numfiles := 8 + numfiles := 6 if len(c.Files) != numfiles { t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) for _, n := range c.Files { @@ -135,10 +144,10 @@ func verifyChart(t *testing.T, c *chart.Chart) { } } - if len(c.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies), c.Dependencies) - for _, d := range c.Dependencies { - t.Logf("\tSubchart: %s\n", d.Metadata.Name) + if len(c.Dependencies()) != 2 { + t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies()) + for _, d := range c.Dependencies() { + 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 { t.Fatalf("expected metadata on dependency: %v", dep) } - exp, ok := expect[dep.Metadata.Name] + exp, ok := expect[dep.Name()] if !ok { - t.Fatalf("Unknown dependency %s", dep.Metadata.Name) + t.Fatalf("Unknown dependency %s", dep.Name()) } 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) { - r, err := LoadRequirements(c) - if err != nil { - t.Fatal(err) - } - if len(r.Dependencies) != 2 { - t.Errorf("Expected 2 requirements, got %d", len(r.Dependencies)) + if len(c.Requirements.Dependencies) != 2 { + t.Errorf("Expected 2 requirements, got %d", len(c.Requirements.Dependencies)) } - tests := []*Dependency{ + 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 := r.Dependencies[i] + d := c.Requirements.Dependencies[i] if d.Name != tt.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) { - r, err := LoadRequirementsLock(c) - if err != nil { - t.Fatal(err) - } - if len(r.Dependencies) != 2 { - t.Errorf("Expected 2 requirements, got %d", len(r.Dependencies)) + if len(c.Requirements.Dependencies) != 2 { + t.Errorf("Expected 2 requirements, got %d", len(c.Requirements.Dependencies)) } - tests := []*Dependency{ + 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 := r.Dependencies[i] + d := c.Requirements.Dependencies[i] if d.Name != tt.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) { - 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 { t.Fatalf("Expected 1 template, got %d", len(c.Templates)) } - if c.Templates[0].Name != "templates/template.tpl" { t.Errorf("Unexpected template: %s", c.Templates[0].Name) } - if len(c.Templates[0].Data) == 0 { 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()) + } + } } diff --git a/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz b/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz new file mode 100644 index 000000000..fb21cd08f Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz/.helmignore b/pkg/chart/loader/testdata/frobnitz/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/Chart.yaml new file mode 100644 index 000000000..134cd1109 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/Chart.yaml @@ -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 diff --git a/pkg/chart/loader/testdata/frobnitz/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz/LICENSE b/pkg/chart/loader/testdata/frobnitz/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz/README.md b/pkg/chart/loader/testdata/frobnitz/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/README.md @@ -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. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md @@ -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`. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml @@ -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" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 000000000..ced5a4a6a Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..0c6980cf7 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml @@ -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"] diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz new file mode 100644 index 000000000..3af333e76 Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz/docs/README.md b/pkg/chart/loader/testdata/frobnitz/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz/icon.svg b/pkg/chart/loader/testdata/frobnitz/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/pkg/chart/loader/testdata/frobnitz/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/chart/loader/testdata/frobnitz/requirements.lock b/pkg/chart/loader/testdata/frobnitz/requirements.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/requirements.lock @@ -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 diff --git a/pkg/chart/loader/testdata/frobnitz/requirements.yaml b/pkg/chart/loader/testdata/frobnitz/requirements.yaml new file mode 100644 index 000000000..5eb0bc98b --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/requirements.yaml @@ -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 diff --git a/pkg/chart/loader/testdata/frobnitz/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz/values.yaml b/pkg/chart/loader/testdata/frobnitz/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz new file mode 100644 index 000000000..692dd6aba Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore b/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore new file mode 100755 index 000000000..9973a57b8 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml new file mode 100755 index 000000000..49df2a046 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml @@ -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 diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt new file mode 100755 index 000000000..2010438c2 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE b/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE new file mode 100755 index 000000000..6121943b1 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/README.md new file mode 100755 index 000000000..8cf4cc3d7 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/README.md @@ -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. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me new file mode 100755 index 000000000..2cecca682 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml new file mode 100755 index 000000000..38a4aaa54 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md new file mode 100755 index 000000000..a7c84fc41 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md @@ -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`. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml new file mode 100755 index 000000000..171e36156 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml new file mode 100755 index 000000000..42c39c262 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml @@ -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" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100755 index 000000000..ced5a4a6a Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml new file mode 100755 index 000000000..0c6980cf7 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml @@ -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"] diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml new file mode 100755 index 000000000..6c2aab7ba --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz new file mode 100755 index 000000000..3af333e76 Binary files /dev/null and b/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz differ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md new file mode 100755 index 000000000..d40747caf --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg b/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg new file mode 100755 index 000000000..892130606 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt new file mode 100755 index 000000000..e69de29bb diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/requirements.lock b/pkg/chart/loader/testdata/frobnitz_backslash/requirements.lock new file mode 100755 index 000000000..6fcc2ed9f --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/requirements.lock @@ -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 diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/requirements.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/requirements.yaml new file mode 100755 index 000000000..5eb0bc98b --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/requirements.yaml @@ -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 diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl new file mode 100755 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml new file mode 100755 index 000000000..61f501258 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/pkg/hapi/chart/metadata.go b/pkg/chart/metadata.go similarity index 100% rename from pkg/hapi/chart/metadata.go rename to pkg/chart/metadata.go diff --git a/pkg/chart/requirements.go b/pkg/chart/requirements.go new file mode 100644 index 000000000..76b8ea8ab --- /dev/null +++ b/pkg/chart/requirements.go @@ -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"` +} diff --git a/pkg/chartutil/capabilities.go b/pkg/chartutil/capabilities.go index 06c117315..fdba95d7d 100644 --- a/pkg/chartutil/capabilities.go +++ b/pkg/chartutil/capabilities.go @@ -20,13 +20,14 @@ import ( "runtime" "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/kubernetes/scheme" tversion "k8s.io/helm/pkg/version" ) var ( // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). - DefaultVersionSet = NewVersionSet("v1") + DefaultVersionSet = allKnownVersions() // DefaultKubeVersion is the default kubernetes version DefaultKubeVersion = &version.Info{ @@ -37,6 +38,12 @@ var ( Compiler: runtime.Compiler, 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. @@ -52,11 +59,11 @@ type Capabilities struct { } // 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. func NewVersionSet(apiVersions ...string) VersionSet { - vs := VersionSet{} + vs := make(VersionSet) for _, v := range apiVersions { vs[v] = struct{}{} } @@ -70,3 +77,11 @@ func (v VersionSet) Has(apiVersion string) bool { _, ok := v[apiVersion] return ok } + +func allKnownVersions() VersionSet { + vs := make(VersionSet) + for gvk := range scheme.Scheme.AllKnownTypes() { + vs[gvk.GroupVersion().String()] = struct{}{} + } + return vs +} diff --git a/pkg/chartutil/capabilities_test.go b/pkg/chartutil/capabilities_test.go index ac20f0038..385a866c9 100644 --- a/pkg/chartutil/capabilities_test.go +++ b/pkg/chartutil/capabilities_test.go @@ -38,9 +38,6 @@ func TestDefaultVersionSet(t *testing.T) { if !DefaultVersionSet.Has("v1") { 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) { diff --git a/pkg/chartutil/chartfile.go b/pkg/chartutil/chartfile.go index 461e47357..e388496f7 100644 --- a/pkg/chartutil/chartfile.go +++ b/pkg/chartutil/chartfile.go @@ -24,29 +24,18 @@ import ( "github.com/ghodss/yaml" "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. func LoadChartfile(filename string) (*chart.Metadata, error) { b, err := ioutil.ReadFile(filename) if err != nil { 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. @@ -80,8 +69,8 @@ func IsChartDir(dirName string) (bool, error) { return false, errors.Errorf("cannot read Chart.Yaml in directory %q", dirName) } - chartContent, err := UnmarshalChartfile(chartYamlContent) - if err != nil { + chartContent := new(chart.Metadata) + if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil { return false, err } if chartContent == nil { diff --git a/pkg/chartutil/chartfile_test.go b/pkg/chartutil/chartfile_test.go index 49de60f65..35be0ab27 100644 --- a/pkg/chartutil/chartfile_test.go +++ b/pkg/chartutil/chartfile_test.go @@ -19,7 +19,7 @@ package chartutil import ( "testing" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" ) 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. - if f.APIVersion != APIVersionv1 { - t.Errorf("Expected API Version %q, got %q", APIVersionv1, f.APIVersion) + if f.APIVersion != chart.APIVersionv1 { + t.Errorf("Expected API Version %q, got %q", chart.APIVersionv1, f.APIVersion) } if f.Name != name { diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index a2e024047..8b27bd2f3 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -21,10 +21,12 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "github.com/pkg/errors" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" ) 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. func CreateFrom(chartfile *chart.Metadata, dest, src string) error { - schart, err := Load(src) + schart, err := loader.Load(src) if err != nil { 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 for _, template := range schart.Templates { - newData := Transform(string(template.Data), "", schart.Metadata.Name) + newData := transform(string(template.Data), schart.Name()) updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData}) } schart.Templates = updatedTemplates - schart.Values = Transform(string(schart.Values), "", schart.Metadata.Name) + schart.Values = transform(string(schart.Values), schart.Name()) return SaveDir(schart, dest) } @@ -378,27 +380,27 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) { { // ingress.yaml path: filepath.Join(cdir, TemplatesDir, IngressFileName), - content: Transform(defaultIngress, "", chartfile.Name), + content: transform(defaultIngress, chartfile.Name), }, { // deployment.yaml path: filepath.Join(cdir, TemplatesDir, DeploymentName), - content: Transform(defaultDeployment, "", chartfile.Name), + content: transform(defaultDeployment, chartfile.Name), }, { // service.yaml path: filepath.Join(cdir, TemplatesDir, ServiceName), - content: Transform(defaultService, "", chartfile.Name), + content: transform(defaultService, chartfile.Name), }, { // NOTES.txt path: filepath.Join(cdir, TemplatesDir, NotesName), - content: Transform(defaultNotes, "", chartfile.Name), + content: transform(defaultNotes, chartfile.Name), }, { // _helpers.tpl path: filepath.Join(cdir, TemplatesDir, HelpersName), - content: Transform(defaultHelpers, "", chartfile.Name), + content: transform(defaultHelpers, chartfile.Name), }, } @@ -413,3 +415,9 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) { } 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, "", replacement, -1)) +} diff --git a/pkg/chartutil/create_test.go b/pkg/chartutil/create_test.go index 01f5902a9..d932c5d1e 100644 --- a/pkg/chartutil/create_test.go +++ b/pkg/chartutil/create_test.go @@ -23,7 +23,8 @@ import ( "strings" "testing" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" ) func TestCreate(t *testing.T) { @@ -42,13 +43,13 @@ func TestCreate(t *testing.T) { dir := filepath.Join(tdir, "foo") - mychart, err := LoadDir(c) + mychart, err := loader.LoadDir(c) if err != nil { t.Fatalf("Failed to load newly created chart %q: %s", c, err) } - if mychart.Metadata.Name != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name) + if mychart.Name() != "foo" { + t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) } for _, d := range []string{TemplatesDir, ChartsDir} { @@ -94,13 +95,13 @@ func TestCreateFrom(t *testing.T) { dir := filepath.Join(tdir, "foo") c := filepath.Join(tdir, cf.Name) - mychart, err := LoadDir(c) + mychart, err := loader.LoadDir(c) if err != nil { t.Fatalf("Failed to load newly created chart %q: %s", c, err) } - if mychart.Metadata.Name != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name) + if mychart.Name() != "foo" { + t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) } 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 { t.Errorf("Expected %s file: %s", f, err) } else if fi.IsDir() { diff --git a/pkg/chartutil/doc.go b/pkg/chartutil/doc.go index 1190d968d..516f4c1cb 100644 --- a/pkg/chartutil/doc.go +++ b/pkg/chartutil/doc.go @@ -16,7 +16,7 @@ limitations under the License. /*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. 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. -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 a chart archive. It will then load accordingly. 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. -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 chartutil // import "k8s.io/helm/pkg/chartutil" diff --git a/pkg/chartutil/expand.go b/pkg/chartutil/expand.go index 126e14e80..7f4fc8bd5 100644 --- a/pkg/chartutil/expand.go +++ b/pkg/chartutil/expand.go @@ -40,8 +40,8 @@ func Expand(dir string, r io.Reader) error { return err } - //split header name and create missing directories - d, _ := filepath.Split(header.Name) + // split header name and create missing directories + d := filepath.Dir(header.Name) fullDir := filepath.Join(dir, d) _, err = os.Stat(fullDir) if err != nil && d != "" { @@ -63,8 +63,7 @@ func Expand(dir string, r io.Reader) error { if err != nil { return err } - _, err = io.Copy(file, tr) - if err != nil { + if _, err = io.Copy(file, tr); err != nil { file.Close() return err } diff --git a/pkg/chartutil/files.go b/pkg/chartutil/files.go index ca149a5e7..af61a24a9 100644 --- a/pkg/chartutil/files.go +++ b/pkg/chartutil/files.go @@ -26,7 +26,7 @@ import ( "github.com/ghodss/yaml" "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. @@ -35,7 +35,7 @@ type Files map[string][]byte // 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. func NewFiles(from []*chart.File) Files { - files := map[string][]byte{} + files := make(map[string][]byte) for _, f := range from { 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 // an empty []byte. func (f Files) GetBytes(name string) []byte { - v, ok := f[name] - if !ok { - return []byte{} + if v, ok := f[name]; ok { + return v } - return v + return []byte{} } // 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. // // 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. // // 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 "" } - m := map[string]string{} + m := make(map[string]string) // Explicitly convert to strings, and file names for k, v := range f { m[path.Base(k)] = string(v) } - return ToYaml(m) + return ToYAML(m) } // 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. // // 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. // // 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 "" } - m := map[string]string{} + m := make(map[string]string) for k, v := range f { 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 @@ -163,11 +162,11 @@ func (f Files) Lines(path string) []string { 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). // // This is designed to be called from a template. -func ToYaml(v interface{}) string { +func ToYAML(v interface{}) string { data, err := yaml.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -176,13 +175,13 @@ func ToYaml(v interface{}) string { 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 // YAML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func FromYaml(str string) map[string]interface{} { +func FromYAML(str string) map[string]interface{} { m := map[string]interface{}{} if err := yaml.Unmarshal([]byte(str), &m); err != nil { @@ -191,11 +190,11 @@ func FromYaml(str string) map[string]interface{} { 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). // // This is designed to be called from a template. -func ToToml(v interface{}) string { +func ToTOML(v interface{}) string { b := bytes.NewBuffer(nil) e := toml.NewEncoder(b) err := e.Encode(v) @@ -205,11 +204,11 @@ func ToToml(v interface{}) 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). // // This is designed to be called from a template. -func ToJson(v interface{}) string { +func ToJSON(v interface{}) string { data, err := json.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -218,14 +217,14 @@ func ToJson(v interface{}) string { 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 // JSON documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func FromJson(str string) map[string]interface{} { - m := map[string]interface{}{} +func FromJSON(str string) map[string]interface{} { + m := make(map[string]interface{}) if err := json.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() diff --git a/pkg/chartutil/files_test.go b/pkg/chartutil/files_test.go index a6c9d1b65..d8174723d 100644 --- a/pkg/chartutil/files_test.go +++ b/pkg/chartutil/files_test.go @@ -97,7 +97,7 @@ func TestLines(t *testing.T) { as.Equal("bar", out[0]) } -func TestToYaml(t *testing.T) { +func TestToYAML(t *testing.T) { expect := "foo: bar" v := struct { Foo string `json:"foo"` @@ -105,12 +105,12 @@ func TestToYaml(t *testing.T) { Foo: "bar", } - if got := ToYaml(v); got != expect { + if got := ToYAML(v); got != expect { t.Errorf("Expected %q, got %q", expect, got) } } -func TestToToml(t *testing.T) { +func TestToTOML(t *testing.T) { expect := "foo = \"bar\"\n" v := struct { Foo string `toml:"foo"` @@ -118,7 +118,7 @@ func TestToToml(t *testing.T) { Foo: "bar", } - if got := ToToml(v); got != expect { + if got := ToTOML(v); got != expect { t.Errorf("Expected %q, got %q", expect, got) } @@ -128,19 +128,19 @@ func TestToToml(t *testing.T) { "sail": "white", }, } - got := ToToml(dict) + got := ToTOML(dict) expect = "[mast]\n sail = \"white\"\n" if got != expect { t.Errorf("Expected:\n%s\nGot\n%s\n", expect, got) } } -func TestFromYaml(t *testing.T) { +func TestFromYAML(t *testing.T) { doc := `hello: world one: two: three ` - dict := FromYaml(doc) + dict := FromYAML(doc) if err, ok := dict["Error"]; ok { t.Fatalf("Parse error: %s", err) } @@ -160,13 +160,13 @@ one: - two - three ` - dict = FromYaml(doc2) + dict = FromYAML(doc2) if _, ok := dict["Error"]; !ok { t.Fatal("Expected parser error") } } -func TestToJson(t *testing.T) { +func TestToJSON(t *testing.T) { expect := `{"foo":"bar"}` v := struct { Foo string `json:"foo"` @@ -174,12 +174,12 @@ func TestToJson(t *testing.T) { Foo: "bar", } - if got := ToJson(v); got != expect { + if got := ToJSON(v); got != expect { t.Errorf("Expected %q, got %q", expect, got) } } -func TestFromJson(t *testing.T) { +func TestFromJSON(t *testing.T) { doc := `{ "hello": "world", "one": { @@ -187,7 +187,7 @@ func TestFromJson(t *testing.T) { } } ` - dict := FromJson(doc) + dict := FromJSON(doc) if err, ok := dict["Error"]; ok { t.Fatalf("Parse error: %s", err) } @@ -209,7 +209,7 @@ func TestFromJson(t *testing.T) { "three" ] ` - dict = FromJson(doc2) + dict = FromJSON(doc2) if _, ok := dict["Error"]; !ok { t.Fatal("Expected parser error") } diff --git a/pkg/chartutil/load.go b/pkg/chartutil/load.go deleted file mode 100644 index 44bcbde03..000000000 --- a/pkg/chartutil/load.go +++ /dev/null @@ -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) -} diff --git a/pkg/chartutil/requirements.go b/pkg/chartutil/requirements.go index e43c44e13..218457cab 100644 --- a/pkg/chartutil/requirements.go +++ b/pkg/chartutil/requirements.go @@ -18,209 +18,88 @@ package chartutil import ( "log" "strings" - "time" "github.com/ghodss/yaml" - "github.com/pkg/errors" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" "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 -func ProcessRequirementsConditions(reqs *Requirements, cvals Values) { - var cond string - var conds []string - if reqs == nil || len(reqs.Dependencies) == 0 { +func ProcessRequirementsConditions(reqs *chart.Requirements, cvals Values) { + if reqs == nil { return } for _, r := range reqs.Dependencies { var hasTrue, hasFalse bool - cond = r.Condition - // check for list - if len(cond) > 0 { - if strings.Contains(cond, ",") { - conds = strings.Split(strings.TrimSpace(cond), ",") - } else { - conds = []string{strings.TrimSpace(cond)} - } - for _, c := range conds { - 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 - } + for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") { + 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 { - log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) + hasFalse = true } - } 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 + } else { + log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) } + } 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 -func ProcessRequirementsTags(reqs *Requirements, cvals Values) { - vt, err := cvals.Table("tags") - if err != nil { +func ProcessRequirementsTags(reqs *chart.Requirements, cvals Values) { + if reqs == nil { return - } - if reqs == nil || len(reqs.Dependencies) == 0 { + vt, err := cvals.Table("tags") + if err != nil { return } for _, r := range reqs.Dependencies { - if len(r.Tags) > 0 { - tags := r.Tags - - var hasTrue, hasFalse bool - for _, k := range tags { - if b, ok := vt[k]; ok { - // if not bool, warn - if bv, ok := b.(bool); ok { - if bv { - hasTrue = true - } else { - hasFalse = true - } + var hasTrue, hasFalse bool + for _, k := range r.Tags { + if b, ok := vt[k]; ok { + // if not bool, warn + if bv, ok := b.(bool); ok { + if bv { + hasTrue = true } 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 - } else if hasTrue || !hasTrue && !hasFalse { - r.Enabled = true - - } - + } + if !hasTrue && hasFalse { + r.Enabled = false + } 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 for _, existingChart := range charts { if existingChart == nil { @@ -248,14 +127,7 @@ func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Ch // ProcessRequirementsEnabled removes disabled charts from dependencies func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error { - reqs, err := LoadRequirements(c) - if err != nil { - // if not just missing requirements file, return error - if nerr, ok := err.(ErrNoRequirementsFile); !ok { - return nerr - } - - // no requirements to process + if c.Requirements == 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 // 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 - for _, req := range reqs.Dependencies { + for _, req := range c.Requirements.Dependencies { if existingDependency.Metadata.Name == req.Name && version.IsCompatibleRange(req.Version, existingDependency.Metadata.Version) { dependencyFound = true break @@ -278,18 +150,18 @@ func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error { } } - for _, req := range reqs.Dependencies { - if chartDependency := getAliasDependency(c.Dependencies, req); chartDependency != nil { + for _, req := range c.Requirements.Dependencies { + if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil { chartDependencies = append(chartDependencies, chartDependency) } if req.Alias != "" { req.Name = req.Alias } } - c.Dependencies = chartDependencies + c.SetDependencies(chartDependencies...) // set all to true - for _, lr := range reqs.Dependencies { + for _, lr := range c.Requirements.Dependencies { lr.Enabled = true } cvals, err := CoalesceValues(c, v) @@ -302,34 +174,32 @@ func ProcessRequirementsEnabled(c *chart.Chart, v []byte) error { return err } // flag dependencies as enabled/disabled - ProcessRequirementsTags(reqs, cvals) - ProcessRequirementsConditions(reqs, cvals) + ProcessRequirementsTags(c.Requirements, cvals) + ProcessRequirementsConditions(c.Requirements, cvals) // make a map of charts to remove - rm := map[string]bool{} - for _, r := range reqs.Dependencies { + rm := map[string]struct{}{} + for _, r := range c.Requirements.Dependencies { if !r.Enabled { // remove disabled chart - rm[r.Name] = true + rm[r.Name] = struct{}{} } } // don't keep disabled charts in new slice cd := []*chart.Chart{} - copy(cd, c.Dependencies[:0]) - for _, n := range c.Dependencies { + copy(cd, c.Dependencies()[:0]) + for _, n := range c.Dependencies() { if _, ok := rm[n.Metadata.Name]; !ok { cd = append(cd, n) } - } + // recursively call self to process sub dependencies for _, t := range cd { - err := ProcessRequirementsEnabled(t, yvals) - // if its not just missing requirements file, return error - if nerr, ok := err.(ErrNoRequirementsFile); !ok && err != nil { - return nerr + if err := ProcessRequirementsEnabled(t, yvals); err != nil { + return err } } - c.Dependencies = cd + c.SetDependencies(cd...) return nil } @@ -361,30 +231,13 @@ func pathToMap(path string, data map[string]interface{}) map[string]interface{} n[i][k] = n[z] } } - 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. func processImportValues(c *chart.Chart) error { - reqs, err := LoadRequirements(c) - if err != nil { - return err + if c.Requirements == nil { + return nil } // combine chart values and empty config to get Values cvals, err := CoalesceValues(c, []byte{}) @@ -393,45 +246,41 @@ func processImportValues(c *chart.Chart) error { } b := make(map[string]interface{}) // import values from each dependency if specified in import-values - for _, r := range reqs.Dependencies { - if len(r.ImportValues) > 0 { - var outiv []interface{} - for _, riv := range r.ImportValues { - switch iv := riv.(type) { - case map[string]interface{}: - nm := map[string]string{ - "child": iv["child"].(string), - "parent": iv["parent"].(string), - } - outiv = append(outiv, nm) - s := r.Name + "." + nm["child"] - // get child table - vv, err := cvals.Table(s) - if err != nil { - 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()) - b = coalesceTables(cvals, vm) - case string: - nm := map[string]string{ - "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()) + for _, r := range c.Requirements.Dependencies { + var outiv []interface{} + for _, riv := range r.ImportValues { + switch iv := riv.(type) { + case map[string]interface{}: + nm := map[string]string{ + "child": iv["child"].(string), + "parent": iv["parent"].(string), + } + outiv = append(outiv, nm) + // get child table + vv, err := cvals.Table(r.Name + "." + nm["child"]) + if err != nil { + 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()) + b = coalesceTables(cvals, vm) + case string: + nm := map[string]string{ + "child": "exports." + iv, + "parent": ".", } + 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) y, err := yaml.Marshal(b) @@ -447,10 +296,11 @@ func processImportValues(c *chart.Chart) error { // ProcessRequirementsImportValues imports specified chart values from child to parent. func ProcessRequirementsImportValues(c *chart.Chart) error { - pc := getParents(c, nil) - for i := len(pc) - 1; i >= 0; i-- { - processImportValues(pc[i]) + for _, d := range c.Dependencies() { + // recurse + if err := ProcessRequirementsImportValues(d); err != nil { + return err + } } - - return nil + return processImportValues(c) } diff --git a/pkg/chartutil/requirements_test.go b/pkg/chartutil/requirements_test.go index 0206f0d2f..f5425e8f7 100644 --- a/pkg/chartutil/requirements_test.go +++ b/pkg/chartutil/requirements_test.go @@ -20,12 +20,13 @@ import ( "strconv" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/version" ) func TestLoadRequirements(t *testing.T) { - c, err := Load("testdata/frobnitz") + c, err := loader.Load("testdata/frobnitz") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } @@ -33,158 +34,89 @@ func TestLoadRequirements(t *testing.T) { } func TestLoadRequirementsLock(t *testing.T) { - c, err := Load("testdata/frobnitz") + c, err := loader.Load("testdata/frobnitz") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } 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 TestRequirementsCombinedDisabledL1(t *testing.T) { - c, err := Load("testdata/subpop") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) +func TestRequirementsEnabled(t *testing.T) { + tests := []struct { + name string + v []byte + e []string // expected charts including duplicates in alphanumeric order + }{{ + "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) { - out := []*chart.Chart{} - err := ProcessRequirementsEnabled(c, v) - if err != nil { + if err := ProcessRequirementsEnabled(c, v); err != nil { t.Errorf("Error processing enabled requirements %v", err) } - out = extractCharts(c, out) + + out := extractCharts(c, nil) // build list of chart names - p := []string{} + var p []string for _, r := range out { - p = append(p, r.Metadata.Name) + p = append(p, r.Name()) } //sort alphanumeric and compare to expectations 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 func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart { - - if len(c.Metadata.Name) > 0 { + if len(c.Name()) > 0 { out = append(out, c) } - for _, d := range c.Dependencies { + for _, d := range c.Dependencies() { out = extractCharts(d, out) } return out } + func TestProcessRequirementsImportValues(t *testing.T) { - c, err := Load("testdata/subpop") + c, err := loader.Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } - v := []byte{} - e := make(map[string]string) e["imported-chart1.SC1bool"] = "true" @@ -279,17 +209,16 @@ func TestProcessRequirementsImportValues(t *testing.T) { e["SCBexported2A"] = "blaster" 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) - if err != nil { - t.Errorf("Error processing import values requirements %v", err) +func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, e map[string]string) { + if err := ProcessRequirementsImportValues(c); err != nil { + t.Fatalf("Error processing import values requirements %v", err) } cc, err := ReadValues(c.Values) if err != nil { - t.Errorf("Error reading import values %v", err) + t.Fatalf("Error reading import values %v", err) } for kk, vv := range e { pv, err := cc.PathValue(kk) @@ -317,182 +246,203 @@ func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v []byte, e ma return } } - } } func TestGetAliasDependency(t *testing.T) { - c, err := Load("testdata/frobnitz") + c, err := loader.Load("testdata/frobnitz") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } - req, err := LoadRequirements(c) - if err != nil { - t.Fatalf("Failed to load requirement for testdata: %s", err) - } + + req := c.Requirements + if len(req.Dependencies) == 0 { t.Fatalf("There are no requirements to test") } // Success case - aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]) + aliasChart := getAliasDependency(c.Dependencies(), req.Dependencies[0]) if aliasChart == nil { t.Fatalf("Failed to get dependency chart for alias %s", req.Dependencies[0].Name) } if req.Dependencies[0].Alias != "" { - if aliasChart.Metadata.Name != req.Dependencies[0].Alias { - t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Alias, aliasChart.Metadata.Name) + if aliasChart.Name() != req.Dependencies[0].Alias { + 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 { - t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.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.Name()) } if req.Dependencies[0].Version != "" { if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { t.Fatalf("Dependency chart version is not in the compatible range") } - } // Failure case req.Dependencies[0].Name = "something-else" - if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) + if aliasChart := getAliasDependency(c.Dependencies(), req.Dependencies[0]); aliasChart != nil { + t.Fatalf("expected no chart but got %s", aliasChart.Name()) } req.Dependencies[0].Version = "something else which is not in the compatible range" 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 ") } - } func TestDependentChartAliases(t *testing.T) { - c, err := Load("testdata/dependent-chart-alias") + c, err := loader.Load("testdata/dependent-chart-alias") if err != nil { 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") } - origLength := len(c.Dependencies) + origLength := len(c.Dependencies()) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { 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") } - reqmts, err := LoadRequirements(c) - if err != nil { - 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)) + if len(c.Dependencies()) != len(c.Requirements.Dependencies) { + t.Fatalf("Expected number of chart dependencies %d, but got %d", len(c.Requirements.Dependencies), len(c.Dependencies())) } - } 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 { t.Fatalf("Failed to load testdata: %s", err) } - if len(c.Dependencies) != 2 { - t.Fatalf("Expected 2 dependencies for this chart, but got %d", len(c.Dependencies)) + if len(c.Dependencies()) != 2 { + 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 { 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") } - } 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) } } func TestDependentChartsWithSubChartsSymlink(t *testing.T) { - c, err := Load("testdata/joonix") + c, err := loader.Load("testdata/joonix") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } - if c.Metadata.Name != "joonix" { - t.Fatalf("Unexpected chart name: %s", c.Metadata.Name) + if c.Name() != "joonix" { + 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) } } 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 { 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") } - origLength := len(c.Dependencies) + origLength := len(c.Dependencies()) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { 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") } - reqmts, err := LoadRequirements(c) - if err != nil { - 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)) + if len(c.Dependencies()) != len(c.Requirements.Dependencies) { + t.Fatalf("Expected number of chart dependencies %d, but got %d", len(c.Requirements.Dependencies), len(c.Dependencies())) } - } 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 { 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") } - origLength := len(c.Dependencies) + origLength := len(c.Dependencies()) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { 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") } - reqmts, err := LoadRequirements(c) - if err != nil { - t.Fatalf("Cannot load requirements for test chart, %v", err) + if len(c.Dependencies()) <= len(c.Requirements.Dependencies) { + t.Fatalf("Expected more dependencies than specified in requirements.yaml(%d), but got %d", len(c.Requirements.Dependencies), len(c.Dependencies())) } +} - if len(c.Dependencies) <= len(reqmts.Dependencies) { - t.Fatalf("Expected more dependencies than specified in requirements.yaml(%d), but got %d", len(reqmts.Dependencies), len(c.Dependencies)) +func verifyRequirements(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) + } + } +} +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) + } + } } diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index 610f0aca0..aac6ab1d4 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -27,7 +27,7 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" ) var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") @@ -35,7 +35,7 @@ var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. func SaveDir(c *chart.Chart, dest string) error { // Create the chart directory - outdir := filepath.Join(dest, c.Metadata.Name) + outdir := filepath.Join(dest, c.Name()) if err := os.Mkdir(outdir, 0755); err != nil { return err } @@ -83,7 +83,7 @@ func SaveDir(c *chart.Chart, dest string) error { // Save dependencies base := filepath.Join(outdir, ChartsDir) - for _, dep := range c.Dependencies { + for _, dep := range c.Dependencies() { // Here, we write each dependency as a tar file. if _, err := Save(dep, base); err != nil { 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 { - base := filepath.Join(prefix, c.Metadata.Name) + base := filepath.Join(prefix, c.Name()) // Save Chart.yaml cdata, err := yaml.Marshal(c.Metadata) @@ -193,7 +193,7 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { } // Save dependencies - for _, dep := range c.Dependencies { + for _, dep := range c.Dependencies() { if err := writeTarContents(out, dep, base+"/charts"); err != nil { return err } @@ -212,8 +212,6 @@ func writeToTar(out *tar.Writer, name string, body []byte) error { if err := out.WriteHeader(h); err != nil { return err } - if _, err := out.Write(body); err != nil { - return err - } - return nil + _, err := out.Write(body) + return err } diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index 04db9cf80..e1760dad5 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -23,7 +23,8 @@ import ( "strings" "testing" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" ) func TestSave(t *testing.T) { @@ -55,13 +56,13 @@ func TestSave(t *testing.T) { t.Fatalf("Expected %q to end with .tgz", where) } - c2, err := LoadFile(where) + c2, err := loader.LoadFile(where) if err != nil { t.Fatal(err) } - if c2.Metadata.Name != c.Metadata.Name { - t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name) + if c2.Name() != c.Name() { + t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) } if !bytes.Equal(c2.Values, c.Values) { t.Fatal("Values data did not match") @@ -93,13 +94,13 @@ func TestSaveDir(t *testing.T) { t.Fatalf("Failed to save: %s", err) } - c2, err := LoadDir(tmp + "/ahab") + c2, err := loader.LoadDir(tmp + "/ahab") if err != nil { t.Fatal(err) } - if c2.Metadata.Name != c.Metadata.Name { - t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name) + if c2.Name() != c.Name() { + t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) } if !bytes.Equal(c2.Values, c.Values) { t.Fatal("Values data did not match") diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index ad499b687..a9195c9f0 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -25,7 +25,7 @@ import ( "github.com/ghodss/yaml" "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. @@ -59,11 +59,10 @@ func (v Values) YAML() (string, error) { // // An ErrNoTable is returned if the table does not exist. func (v Values) Table(name string) (Values, error) { - names := strings.Split(name, ".") table := v var err error - for _, n := range names { + for _, n := range strings.Split(name, ".") { table, err = tableLookup(table, n) if err != nil { 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) - return map[string]interface{}{}, e + return Values{}, e } // 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 // the values destined for its dependencies. func CoalesceValues(chrt *chart.Chart, vals []byte) (Values, error) { + var err error cvals := Values{} // 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. if vals != nil { - evals, err := ReadValues(vals) - if err != nil { - return cvals, err - } - cvals, err = coalesce(chrt, evals) + cvals, err = ReadValues(vals) if err != nil { return cvals, err } } - var err error - cvals, err = coalesceDeps(chrt, cvals) - return cvals, err + cvals, err = coalesce(chrt, cvals) + if err != nil { + return cvals, err + } + + return coalesceDeps(chrt, cvals) } // 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. func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { - for _, subchart := range chrt.Dependencies { - if c, ok := dest[subchart.Metadata.Name]; !ok { + for _, subchart := range chrt.Dependencies() { + if c, ok := dest[subchart.Name()]; !ok { // 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) { - 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{}) // 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 // 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 { return dest, err } @@ -206,14 +205,14 @@ func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} { var dg, sg map[string]interface{} if destglob, ok := dest[GlobalKey]; !ok { - dg = map[string]interface{}{} + dg = make(map[string]interface{}) } else if dg, ok = destglob.(map[string]interface{}); !ok { log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey) return dg } if srcglob, ok := src[GlobalKey]; !ok { - sg = map[string]interface{}{} + sg = make(map[string]interface{}) } else if sg, ok = srcglob.(map[string]interface{}); !ok { log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey) return dg @@ -340,19 +339,8 @@ type ReleaseOptions struct { // 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. -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{}{ "Release": map[string]interface{}{ diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go index 8fe1b3be5..907dba575 100644 --- a/pkg/chartutil/values_test.go +++ b/pkg/chartutil/values_test.go @@ -25,7 +25,8 @@ import ( 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" ) @@ -89,16 +90,14 @@ where: Metadata: &chart.Metadata{Name: "test"}, Templates: []*chart.File{}, Values: []byte(chartValues), - Dependencies: []*chart.Chart{ - { - Metadata: &chart.Metadata{Name: "where"}, - Values: []byte{}, - }, - }, Files: []*chart.File{ {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, }, } + c.AddDependency(&chart.Chart{ + Metadata: &chart.Metadata{Name: "where"}, + Values: []byte{}, + }) v := []byte(overideValues) o := ReleaseOptions{ @@ -112,7 +111,7 @@ where: KubeVersion: &kversion.Info{Major: "1"}, } - res, err := ToRenderValuesCaps(c, v, o, caps) + res, err := ToRenderValues(c, v, o, caps) if err != nil { 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) { var b bytes.Buffer tt := template.Must(template.New("t").Parse(tpl)) - if err := tt.Execute(&b, v); err != nil { - return "", err - } - return b.String(), nil + err := tt.Execute(&b, v) + return b.String(), err } // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 @@ -293,7 +290,7 @@ pequod: func TestCoalesceValues(t *testing.T) { tchart := "testdata/moby" - c, err := LoadDir(tchart) + c, err := loader.LoadDir(tchart) if err != nil { t.Fatal(err) } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index a59e7dc0d..d085966dc 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -30,9 +30,10 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/getter" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/repo" "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 // an update. - lock, err := chartutil.LoadRequirementsLock(c) - if err != nil { + lock := c.RequirementsLock + if lock == nil { return m.Update() } // A lock must accompany a requirements.yaml file. - req, err := chartutil.LoadRequirements(c) - if err != nil { - return errors.Wrap(err, "requirements.yaml cannot be opened") - } + req := c.Requirements if sum, err := resolver.HashReq(req); err != nil || sum != lock.Digest { 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 // completion. - req, err := chartutil.LoadRequirements(c) - if err != nil { - if err == chartutil.ErrRequirementsNotFound { - fmt.Fprintf(m.Out, "No requirements found in %s/charts.\n", m.ChartPath) - return nil - } - return err + req := c.Requirements + if req == nil { + return nil } // 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. - oldLock, err := chartutil.LoadRequirementsLock(c) - if err == nil && oldLock.Digest == lock.Digest { + oldLock := c.RequirementsLock + if oldLock != nil && oldLock.Digest == lock.Digest { return nil } @@ -176,13 +170,13 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) { } else if !fi.IsDir() { 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. // // 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) 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 // a conflict. -func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { +func (m *Manager) downloadAll(deps []*chart.Dependency) error { repos, err := m.loadChartRepositories() if err != nil { return err @@ -307,12 +301,12 @@ func (m *Manager) safeDeleteDep(name, dir string) error { return err } for _, fname := range files { - ch, err := chartutil.LoadFile(fname) + ch, err := loader.LoadFile(fname) if err != nil { fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err) continue } - if ch.Metadata.Name != name { + if ch.Name() != name { // This is not the file you are looking for. 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. -func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error { +func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile()) if err != nil { 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 // by Helm. missing := []string{} +Loop: for _, dd := range deps { // If repo is from local path, continue if strings.HasPrefix(dd.Repository, "file://") { continue } - found := false if dd.Repository == "" { - found = true - } else { - for _, repo := range repos { - if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { - found = true - } - } + continue } - if !found { - missing = append(missing, dd.Repository) + for _, repo := range repos { + if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { + continue Loop + } } + missing = append(missing, dd.Repository) } if len(missing) > 0 { 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. -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()) if err != nil { 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 { - 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. - // For this case, let's give them a suggestion. - containsNonURL := false - for _, repo := range missing { - if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") { - containsNonURL = true - } + 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. + // For this case, let's give them a suggestion. + containsNonURL := false + for _, repo := range missing { + if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") { + containsNonURL = true } - if containsNonURL { - errorMessage += ` + } + if containsNonURL { + errorMessage += ` 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 "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 } @@ -596,7 +585,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err } // 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) if err != nil { return err @@ -618,7 +607,7 @@ func tarFromLocalDir(chartpath, name, repo, version string) (string, error) { return "", err } - ch, err := chartutil.LoadDir(origPath) + ch, err := loader.LoadDir(origPath) if err != nil { return "", err } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 1ff2a9c17..f5a252d0b 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -20,7 +20,7 @@ import ( "reflect" "testing" - "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/helm/helmpath" ) @@ -100,48 +100,48 @@ func TestGetRepoNames(t *testing.T) { } tests := []struct { name string - req []*chartutil.Dependency + req []*chart.Dependency expect map[string]string err bool }{ { name: "no repo definition failure", - req: []*chartutil.Dependency{ + req: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "http://example.com/test"}, }, err: true, }, { name: "no repo definition failure -- stable repo", - req: []*chartutil.Dependency{ + req: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "stable"}, }, err: true, }, { name: "no repo definition failure", - req: []*chartutil.Dependency{ + req: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "http://example.com"}, }, expect: map[string]string{"oedipus-rex": "testing"}, }, { name: "repo from local path", - req: []*chartutil.Dependency{ + req: []*chart.Dependency{ {Name: "local-dep", Repository: "file://./testdata/signtest"}, }, expect: map[string]string{"local-dep": "file://./testdata/signtest"}, }, { name: "repo alias (alias:)", - req: []*chartutil.Dependency{ + req: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "alias:testing"}, }, expect: map[string]string{"oedipus-rex": "testing"}, }, { name: "repo alias (@)", - req: []*chartutil.Dependency{ + req: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "@testing"}, }, expect: map[string]string{"oedipus-rex": "testing"}, diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index f09a8ec7c..2b330ecf3 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -17,7 +17,6 @@ limitations under the License. package engine import ( - "bytes" "path" "sort" "strings" @@ -26,19 +25,19 @@ import ( "github.com/Masterminds/sprig" "github.com/pkg/errors" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" ) // Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. type Engine struct { // FuncMap contains the template functions that will be passed to each // 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 // a value that was not passed in. Strict bool - CurrentTemplates map[string]renderable + currentTemplates map[string]renderable } // 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 // access to the underlying OS (env, expandenv). func New() *Engine { - f := FuncMap() - return &Engine{ - FuncMap: f, - } + return &Engine{funcMap: FuncMap()} } // FuncMap returns a mapping of all of the functions that Engine has. @@ -76,11 +72,11 @@ func FuncMap() template.FuncMap { // Add some extra functionality extra := template.FuncMap{ - "toToml": chartutil.ToToml, - "toYaml": chartutil.ToYaml, - "fromYaml": chartutil.FromYaml, - "toJson": chartutil.ToJson, - "fromJson": chartutil.FromJson, + "toToml": chartutil.ToTOML, + "toYaml": chartutil.ToYAML, + "fromYaml": chartutil.FromYAML, + "toJson": chartutil.ToJSON, + "fromJson": chartutil.FromJSON, // This is a placeholder for the "include" function, which is // 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) { // Render the charts tmap := allTemplates(chrt, values) - e.CurrentTemplates = tmap - return e.render(tmap) + e.currentTemplates = tmap + return e.render(chrt, tmap) } // 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. func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap { // Clone the func map because we are adding context-specific functions. - var funcMap template.FuncMap = map[string]interface{}{} - for k, v := range e.FuncMap { + funcMap := make(template.FuncMap) + for k, v := range e.funcMap { funcMap[k] = v } // Add the 'include' function here so we can close over t. funcMap["include"] = func(name string, data interface{}) (string, error) { - buf := bytes.NewBuffer(nil) - if err := t.ExecuteTemplate(buf, name, data); err != nil { - return "", err - } - return buf.String(), nil + var buf strings.Builder + err := t.ExecuteTemplate(&buf, name, data) + return buf.String(), err } // Add the 'required' function here @@ -177,15 +171,15 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap { basePath: basePath.(string), } - templates := map[string]renderable{} templateName, err := vals.PathValue("Template.Name") if err != nil { return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl) } + templates := make(map[string]renderable) templates[templateName.(string)] = r - result, err := e.render(templates) + result, err := e.render(nil, templates) if err != nil { 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. -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 // 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. @@ -228,8 +222,7 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, for _, fname := range keys { r := tpls[fname] - t = t.New(fname).Funcs(funcMap) - if _, err := t.Parse(r.tpl); err != nil { + if _, err := t.New(fname).Funcs(funcMap).Parse(r.tpl); err != nil { return map[string]string{}, errors.Wrapf(err, "parse error in %q", 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 // 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 { - t = t.New(fname).Funcs(funcMap) - if _, err := t.Parse(r.tpl); err != nil { + if _, err := t.New(fname).Funcs(funcMap).Parse(r.tpl); err != nil { return map[string]string{}, errors.Wrapf(err, "parse error in %q", fname) } } } rendered = make(map[string]string, len(files)) - var buf bytes.Buffer for _, file := range files { // Don't render partials. We don't care out the direct output of partials. // 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. 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 { 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 "" even if Options(missing=zero) // is set. Since missing=error will never get here, we do not need to handle // the Strict case. - rendered[file] = strings.Replace(buf.String(), "", "", -1) - buf.Reset() + f := &chart.File{ + Name: strings.Replace(file, "/templates", "/manifests", -1), + Data: []byte(strings.Replace(buf.String(), "", "", -1)), + } + rendered[file] = string(f.Data) + if ch != nil { + ch.Files = append(ch.Files, f) + } } 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. func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { - templates := map[string]renderable{} - recAllTpls(c, templates, vals, true, "") + templates := make(map[string]renderable) + recAllTpls(c, templates, vals) 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 // 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 // values are appended later. - cvals := chartutil.Values{} - if top { - // If this is the top of the rendering tree, assume that parentVals - // is already resolved to the authoritative values. + cvals := make(chartutil.Values) + if c.IsRoot() { cvals = parentVals - } else if c.Metadata != nil && c.Metadata.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 - } - } - + } else if c.Name() != "" { cvals = map[string]interface{}{ - "Values": newVals, + "Values": make(chartutil.Values), "Release": parentVals["Release"], "Chart": c.Metadata, "Files": chartutil.NewFiles(c.Files), "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 - if parentID != "" { - // 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() { + recAllTpls(child, templates, cvals) } - for _, child := range c.Dependencies { - recAllTpls(child, templates, cvals, false, newParentID) - } + newParentID := c.ChartFullPath() for _, t := range c.Templates { templates[path.Join(newParentID, t.Name)] = renderable{ tpl: string(t.Data), diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index be9aaa448..3b1127d45 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -21,8 +21,8 @@ import ( "sync" "testing" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" ) func TestSortTemplates(t *testing.T) { @@ -62,7 +62,7 @@ func TestEngine(t *testing.T) { // Forbidden because they allow access to the host OS. forbidden := []string{"env", "expandenv"} 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) } } @@ -149,7 +149,7 @@ func TestRenderInternals(t *testing.T) { "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, } - out, err := e.render(tpls) + out, err := e.render(nil, tpls) if err != nil { t.Fatalf("Failed template rendering: %s", err) } @@ -182,7 +182,7 @@ func TestParallelRenderInternals(t *testing.T) { tt := fmt.Sprintf("expect-%d", i) v := chartutil.Values{"val": tt} tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}} - out, err := e.render(tpls) + out, err := e.render(nil, tpls) if err != nil { 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/bar", Data: []byte("bar")}, }, - Dependencies: []*chart.Chart{ - { - Metadata: &chart.Metadata{Name: "laboratory mice"}, - Templates: []*chart.File{ - {Name: "templates/pinky", Data: []byte("pinky")}, - {Name: "templates/brain", Data: []byte("brain")}, - }, - Dependencies: []*chart.Chart{{ - Metadata: &chart.Metadata{Name: "same thing we do every night"}, - Templates: []*chart.File{ - {Name: "templates/innermost", Data: []byte("innermost")}, - }}, - }, - }, + } + dep1 := &chart.Chart{ + Metadata: &chart.Metadata{Name: "laboratory mice"}, + Templates: []*chart.File{ + {Name: "templates/pinky", Data: []byte("pinky")}, + {Name: "templates/brain", Data: []byte("brain")}, + }, + } + ch1.AddDependency(dep1) + + 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 tpls := allTemplates(ch1, v) @@ -235,18 +236,15 @@ func TestRenderDependency(t *testing.T) { Templates: []*chart.File{ {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{}{}) - if err != nil { t.Fatalf("failed to render chart: %s", err) } @@ -285,9 +283,9 @@ func TestRenderNestedValues(t *testing.T) { Templates: []*chart.File{ {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, }, - Values: []byte(`who: "Robert"`), - Dependencies: []*chart.Chart{deepest}, + Values: []byte(`who: "Robert"`), } + inner.AddDependency(deepest) outer := &chart.Chart{ Metadata: &chart.Metadata{Name: "top"}, @@ -299,8 +297,8 @@ what: stinkweed who: me herrick: who: time`), - Dependencies: []*chart.Chart{inner}, } + outer.AddDependency(inner) injValues := []byte(` what: rosebuds @@ -358,8 +356,6 @@ func TestRenderBuiltinValues(t *testing.T) { {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"}}`)}, }, - Values: []byte{}, - Dependencies: []*chart.Chart{}, Files: []*chart.File{ {Name: "author", Data: []byte("Virgil")}, {Name: "book/title.txt", Data: []byte("Aeneid")}, @@ -371,9 +367,8 @@ func TestRenderBuiltinValues(t *testing.T) { Templates: []*chart.File{ {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, }, - Values: []byte{}, - Dependencies: []*chart.Chart{inner}, } + outer.AddDependency(inner) inject := chartutil.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{ Metadata: &chart.Metadata{Name: "conrad"}, Templates: []*chart.File{ {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, }, - Values: []byte{}, - Dependencies: []*chart.Chart{}, } v := chartutil.Values{ @@ -431,127 +424,127 @@ func TestAlterFuncMap(t *testing.T) { if got := out["conrad/templates/quote"]; got != expect { 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"}, Templates: []*chart.File{ {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!`)}, }, - Values: []byte{}, - Dependencies: []*chart.Chart{}, } - reqValues := chartutil.Values{ + v := chartutil.Values{ "Values": chartutil.Values{ "who": "us", "bases": 2, }, - "Chart": reqChart.Metadata, + "Chart": c.Metadata, "Release": chartutil.Values{ "Name": "That 90s meme", }, } - outReq, err := New().Render(reqChart, reqValues) + out, err := New().Render(c, v) if err != nil { t.Fatal(err) } expectStr := "All your base are belong to us" - if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq) + if gotStr := out["conan/templates/quote"]; gotStr != expectStr { + t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) } expectNum := "All 2 of them!" - if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq) + if gotNum := out["conan/templates/bases"]; gotNum != expectNum { + 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"}, Templates: []*chart.File{ {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{ "value": "myvalue", }, - "Chart": tplChart.Metadata, + "Chart": c.Metadata, "Release": chartutil.Values{ "Name": "TestRelease", }, } - outTpl, err := New().Render(tplChart, tplValues) + out, err := New().Render(c, v) if err != nil { t.Fatal(err) } - expectTplStr := "Evaluate tpl Value: myvalue" - if gotStrTpl := outTpl["TplFunction/templates/base"]; gotStrTpl != expectTplStr { - t.Errorf("Expected %q, got %q (%v)", expectTplStr, gotStrTpl, outTpl) + expect := "Evaluate tpl Value: myvalue" + if got := out["TplFunction/templates/base"]; got != expect { + 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"}, Templates: []*chart.File{ {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{ "value": "myvalue", }, - "Chart": tplChartWithFunction.Metadata, + "Chart": c.Metadata, "Release": chartutil.Values{ "Name": "TestRelease", }, } - outTplWithFunction, err := New().Render(tplChartWithFunction, tplValuesWithFunction) + out, err := New().Render(c, v) if err != nil { t.Fatal(err) } - expectTplStrWithFunction := "Evaluate tpl Value: \"myvalue\"" - if gotStrTplWithFunction := outTplWithFunction["TplFunction/templates/base"]; gotStrTplWithFunction != expectTplStrWithFunction { - t.Errorf("Expected %q, got %q (%v)", expectTplStrWithFunction, gotStrTplWithFunction, outTplWithFunction) + expect := "Evaluate tpl Value: \"myvalue\"" + if got := out["TplFunction/templates/base"]; got != expect { + 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"}, Templates: []*chart.File{ {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, }, - Values: []byte{}, - Dependencies: []*chart.Chart{}, } - tplValueWithInclude := chartutil.Values{ + v := chartutil.Values{ "Values": chartutil.Values{ "value": "myvalue", }, - "Chart": tplChartWithInclude.Metadata, + "Chart": c.Metadata, "Release": chartutil.Values{ "Name": "TestRelease", }, } - outTplWithInclude, err := New().Render(tplChartWithInclude, tplValueWithInclude) + out, err := New().Render(c, v) if err != nil { t.Fatal(err) } - expectedTplStrWithInclude := "\"TplFunction/templates/base\"" - if gotStrTplWithInclude := outTplWithInclude["TplFunction/templates/base"]; gotStrTplWithInclude != expectedTplStrWithInclude { - t.Errorf("Expected %q, got %q (%v)", expectedTplStrWithInclude, gotStrTplWithInclude, outTplWithInclude) + expect := "\"TplFunction/templates/base\"" + if got := out["TplFunction/templates/base"]; got != expect { + t.Errorf("Expected %q, got %q (%v)", expect, got, out) } } diff --git a/pkg/hapi/chart/chart.go b/pkg/hapi/chart/chart.go deleted file mode 100644 index 711bee61d..000000000 --- a/pkg/hapi/chart/chart.go +++ /dev/null @@ -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"` -} diff --git a/pkg/hapi/release/release.go b/pkg/hapi/release/release.go index f8b739468..b850f74a6 100644 --- a/pkg/hapi/release/release.go +++ b/pkg/hapi/release/release.go @@ -15,7 +15,7 @@ limitations under the License. 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 // and the variables used to deploy that chart. diff --git a/pkg/hapi/tiller.go b/pkg/hapi/tiller.go index ee30f5619..f0fc89a1f 100644 --- a/pkg/hapi/tiller.go +++ b/pkg/hapi/tiller.go @@ -16,7 +16,7 @@ limitations under the License. package hapi import ( - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/hapi/release" ) diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 8925b6043..83b3c8e49 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -17,13 +17,13 @@ limitations under the License. package helm // import "k8s.io/helm/pkg/helm" import ( + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/tiller" - "k8s.io/helm/pkg/tiller/environment" ) // Client manages client side of the Helm-Tiller protocol. @@ -39,8 +39,7 @@ func NewClient(opts ...Option) *Client { } func (c *Client) init() *Client { - env := environment.New() - c.tiller = tiller.NewReleaseServer(env, c.opts.discovery, c.opts.kubeClient) + c.tiller = tiller.NewReleaseServer(c.opts.discovery, c.opts.kubeClient) c.tiller.Releases = storage.Init(c.opts.driver) 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. func (c *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*release.Release, error) { // load the chart to install - chart, err := chartutil.Load(chstr) + chart, err := loader.Load(chstr) if err != nil { 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. func (c *Client) UpdateRelease(rlsName, chstr string, opts ...UpdateOption) (*release.Release, error) { // load the chart to update - chart, err := chartutil.Load(chstr) + chart, err := loader.Load(chstr) if err != nil { return nil, err } diff --git a/pkg/helm/fake.go b/pkg/helm/fake.go index d7f5e01fd..03f374700 100644 --- a/pkg/helm/fake.go +++ b/pkg/helm/fake.go @@ -23,8 +23,8 @@ import ( "github.com/pkg/errors" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" ) diff --git a/pkg/helm/fake_test.go b/pkg/helm/fake_test.go index 12114328a..97f127507 100644 --- a/pkg/helm/fake_test.go +++ b/pkg/helm/fake_test.go @@ -20,8 +20,8 @@ import ( "reflect" "testing" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" ) diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go index 94207077f..d2a369488 100644 --- a/pkg/helm/helm_test.go +++ b/pkg/helm/helm_test.go @@ -23,9 +23,9 @@ import ( "github.com/pkg/errors" - "k8s.io/helm/pkg/chartutil" + cpb "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/hapi" - cpb "k8s.io/helm/pkg/hapi/chart" rls "k8s.io/helm/pkg/hapi/release" ) @@ -349,7 +349,7 @@ func assert(t *testing.T, expect, actual interface{}) { } func loadChart(t *testing.T, name string) *cpb.Chart { - c, err := chartutil.Load(filepath.Join(chartsDir, name)) + c, err := loader.Load(filepath.Join(chartsDir, name)) if err != nil { t.Fatalf("failed to load test chart (%q): %s\n", name, err) } diff --git a/pkg/helm/interface.go b/pkg/helm/interface.go index 25b96a0a8..fff653fba 100644 --- a/pkg/helm/interface.go +++ b/pkg/helm/interface.go @@ -17,8 +17,8 @@ limitations under the License. package helm import ( + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" ) diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go new file mode 100644 index 000000000..b1093a737 --- /dev/null +++ b/pkg/kube/converter.go @@ -0,0 +1,40 @@ +/* +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 kube // import "k8s.io/helm/pkg/kube" + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/api/legacyscheme" +) + +// AsDefaultVersionedOrOriginal returns the object as a Go object in the external form if possible (matching the +// group version kind of the mapping if provided, a best guess based on serialization if not provided, or obj if it cannot be converted. +// TODO update call sites to specify the scheme they want on their builder. +func AsDefaultVersionedOrOriginal(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { + converter := runtime.ObjectConvertor(legacyscheme.Scheme) + groupVersioner := runtime.GroupVersioner(schema.GroupVersions(legacyscheme.Scheme.PrioritizedVersionsAllGroups())) + if mapping != nil { + groupVersioner = mapping.GroupVersionKind.GroupVersion() + } + + if obj, err := converter.ConvertToVersion(obj, groupVersioner); err == nil { + return obj + } + return obj +} diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go index 30691c500..9f39253e8 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -24,8 +24,8 @@ import ( "github.com/asaskevich/govalidator" "github.com/pkg/errors" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/lint/support" ) diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go index 4af73422e..a2bf8c0fa 100644 --- a/pkg/lint/rules/chartfile_test.go +++ b/pkg/lint/rules/chartfile_test.go @@ -24,8 +24,8 @@ import ( "github.com/pkg/errors" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/lint/support" ) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 6b2a75027..fe1168ccd 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -23,10 +23,10 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/lint/support" - tversion "k8s.io/helm/pkg/version" ) // Templates lints the templates in the Linter. @@ -42,7 +42,7 @@ func Templates(linter *support.Linter, values []byte, namespace string, strict b } // Load chart and parse templates, based on tiller/release_server - chart, err := chartutil.Load(linter.ChartDir) + chart, err := loader.Load(linter.ChartDir) chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err) @@ -51,11 +51,7 @@ func Templates(linter *support.Linter, values []byte, namespace string, strict b } options := chartutil.ReleaseOptions{Name: "testRelease"} - caps := &chartutil.Capabilities{ - APIVersions: chartutil.DefaultVersionSet, - KubeVersion: chartutil.DefaultKubeVersion, - HelmVersion: tversion.GetBuildInfo(), - } + cvals, err := chartutil.CoalesceValues(chart, values) if err != nil { return @@ -65,7 +61,8 @@ func Templates(linter *support.Linter, values []byte, namespace string, strict b if err != nil { return } - valuesToRender, err := chartutil.ToRenderValuesCaps(chart, yvals, options, caps) + caps := chartutil.DefaultCapabilities + valuesToRender, err := chartutil.ToRenderValues(chart, yvals, options, caps) if err != nil { // FIXME: This seems to generate a duplicate, but I can't find where the first // error is coming from. @@ -109,7 +106,7 @@ func Templates(linter *support.Linter, values []byte, namespace string, strict b // NOTE: disabled for now, Refs https://github.com/kubernetes/helm/issues/1037 // linter.RunLinterRule(support.WarningSev, path, validateQuotes(string(preExecutedTemplate))) - renderedContent := renderedContentMap[filepath.Join(chart.Metadata.Name, fileName)] + renderedContent := renderedContentMap[filepath.Join(chart.Name(), fileName)] var yamlStruct K8sYamlStruct // Even though K8sYamlStruct only defines Metadata namespace, an error in any other // key will be raised as well diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 4d1803454..62e9462c1 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -31,8 +31,8 @@ import ( "golang.org/x/crypto/openpgp/clearsign" "golang.org/x/crypto/openpgp/packet" - "k8s.io/helm/pkg/chartutil" - hapi "k8s.io/helm/pkg/hapi/chart" + hapi "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" ) var defaultPGPConfig = packet.Config{ @@ -317,7 +317,7 @@ func messageBlock(chartpath string) (*bytes.Buffer, error) { } // Load the archive into memory. - chart, err := chartutil.LoadFile(chartpath) + chart, err := loader.LoadFile(chartpath) if err != nil { return b, err } diff --git a/pkg/releaseutil/manifest.go b/pkg/releaseutil/manifest.go index a0449cc55..d233fc106 100644 --- a/pkg/releaseutil/manifest.go +++ b/pkg/releaseutil/manifest.go @@ -46,7 +46,6 @@ func SplitManifests(bigFile string) map[string]string { docs := sep.Split(bigFileTmp, -1) var count int for _, d := range docs { - if d == "" { continue } diff --git a/pkg/releaseutil/sorter.go b/pkg/releaseutil/sorter.go index cd90e5816..31f8367dd 100644 --- a/pkg/releaseutil/sorter.go +++ b/pkg/releaseutil/sorter.go @@ -22,14 +22,28 @@ import ( rspb "k8s.io/helm/pkg/hapi/release" ) -type sorter struct { - list []*rspb.Release - less func(int, int) bool +type list []*rspb.Release + +func (s list) Len() int { return len(s) } +func (s list) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type ByName struct{ list } + +func (s ByName) Less(i, j int) bool { return s.list[i].Name < s.list[j].Name } + +type ByDate struct{ list } + +func (s ByDate) Less(i, j int) bool { + ti := s.list[i].Info.LastDeployed.Second() + tj := s.list[j].Info.LastDeployed.Second() + return ti < tj } -func (s *sorter) Len() int { return len(s.list) } -func (s *sorter) Less(i, j int) bool { return s.less(i, j) } -func (s *sorter) Swap(i, j int) { s.list[i], s.list[j] = s.list[j], s.list[i] } +type ByRevision struct{ list } + +func (s ByRevision) Less(i, j int) bool { + return s.list[i].Version < s.list[j].Version +} // Reverse reverses the list of releases sorted by the sort func. func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { @@ -42,36 +56,17 @@ func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { // SortByName returns the list of releases sorted // in lexicographical order. func SortByName(list []*rspb.Release) { - s := &sorter{list: list} - s.less = func(i, j int) bool { - ni := s.list[i].Name - nj := s.list[j].Name - return ni < nj - } - sort.Sort(s) + sort.Sort(ByName{list}) } // SortByDate returns the list of releases sorted by a // release's last deployed time (in seconds). func SortByDate(list []*rspb.Release) { - s := &sorter{list: list} - - s.less = func(i, j int) bool { - ti := s.list[i].Info.LastDeployed.Second() - tj := s.list[j].Info.LastDeployed.Second() - return ti < tj - } - sort.Sort(s) + sort.Sort(ByDate{list}) } // SortByRevision returns the list of releases sorted by a // release's revision number (release.Version). func SortByRevision(list []*rspb.Release) { - s := &sorter{list: list} - s.less = func(i, j int) bool { - vi := s.list[i].Version - vj := s.list[j].Version - return vi < vj - } - sort.Sort(s) + sort.Sort(ByRevision{list}) } diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 708b15043..03d89fe2f 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -27,7 +27,7 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" - "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/provenance" ) @@ -172,7 +172,7 @@ func (r *ChartRepository) saveIndexFile() error { func (r *ChartRepository) generateIndex() error { for _, path := range r.ChartPaths { - ch, err := chartutil.Load(path) + ch, err := loader.Load(path) if err != nil { return err } @@ -182,7 +182,7 @@ func (r *ChartRepository) generateIndex() error { return err } - if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) { + if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) { r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest) } // TODO: If a chart exists, but has a different Digest, should we error? diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index b1c066ced..01563a31e 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -27,8 +27,8 @@ import ( "testing" "time" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/getter" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/helm/environment" ) diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 9cd6159fc..d64b065b8 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -30,8 +30,8 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" - "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/provenance" "k8s.io/helm/pkg/urlutil" ) @@ -251,7 +251,7 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) { parentURL = filepath.Join(baseURL, parentDir) } - c, err := chartutil.Load(arch) + c, err := loader.Load(arch) if err != nil { // Assume this is not a chart. continue diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index c3199290a..68f8c7176 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -22,8 +22,8 @@ import ( "path/filepath" "testing" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/getter" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/helm/environment" ) diff --git a/pkg/resolver/resolver.go b/pkg/resolver/resolver.go index 2d2fba018..4df51181a 100644 --- a/pkg/resolver/resolver.go +++ b/pkg/resolver/resolver.go @@ -26,7 +26,7 @@ import ( "github.com/Masterminds/semver" "github.com/pkg/errors" - "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/provenance" "k8s.io/helm/pkg/repo" @@ -47,10 +47,10 @@ func New(chartpath string, helmhome helmpath.Home) *Resolver { } // Resolve resolves dependencies and returns a lock file with the resolution. -func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]string, d string) (*chartutil.RequirementsLock, error) { +func (r *Resolver) Resolve(reqs *chart.Requirements, repoNames map[string]string, d string) (*chart.RequirementsLock, error) { // Now we clone the dependencies, locking as we go. - locked := make([]*chartutil.Dependency, len(reqs.Dependencies)) + locked := make([]*chart.Dependency, len(reqs.Dependencies)) missing := []string{} for i, d := range reqs.Dependencies { if strings.HasPrefix(d.Repository, "file://") { @@ -59,7 +59,7 @@ func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]st return nil, err } - locked[i] = &chartutil.Dependency{ + locked[i] = &chart.Dependency{ Name: d.Name, Repository: d.Repository, Version: d.Version, @@ -81,7 +81,7 @@ func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]st return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository) } - locked[i] = &chartutil.Dependency{ + locked[i] = &chart.Dependency{ Name: d.Name, Repository: d.Repository, } @@ -107,7 +107,7 @@ func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]st if len(missing) > 0 { return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in requirements.yaml", strings.Join(missing, ", ")) } - return &chartutil.RequirementsLock{ + return &chart.RequirementsLock{ Generated: time.Now(), Digest: d, Dependencies: locked, @@ -118,7 +118,7 @@ func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]st // // This should be used only to compare against another hash generated by this // function. -func HashReq(req *chartutil.Requirements) (string, error) { +func HashReq(req *chart.Requirements) (string, error) { data, err := json.Marshal(req) if err != nil { return "", err diff --git a/pkg/resolver/resolver_test.go b/pkg/resolver/resolver_test.go index 78a0bc46c..decf3b59a 100644 --- a/pkg/resolver/resolver_test.go +++ b/pkg/resolver/resolver_test.go @@ -18,20 +18,20 @@ package resolver import ( "testing" - "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/chart" ) func TestResolve(t *testing.T) { tests := []struct { name string - req *chartutil.Requirements - expect *chartutil.RequirementsLock + req *chart.Requirements + expect *chart.RequirementsLock err bool }{ { name: "version failure", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "http://example.com", Version: ">a1"}, }, }, @@ -39,8 +39,8 @@ func TestResolve(t *testing.T) { }, { name: "cache index failure", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "oedipus-rex", Repository: "http://example.com", Version: "1.0.0"}, }, }, @@ -48,8 +48,8 @@ func TestResolve(t *testing.T) { }, { name: "chart not found failure", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "redis", Repository: "http://example.com", Version: "1.0.0"}, }, }, @@ -57,8 +57,8 @@ func TestResolve(t *testing.T) { }, { name: "constraint not satisfied failure", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "alpine", Repository: "http://example.com", Version: ">=1.0.0"}, }, }, @@ -66,34 +66,34 @@ func TestResolve(t *testing.T) { }, { name: "valid lock", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "alpine", Repository: "http://example.com", Version: ">=0.1.0"}, }, }, - expect: &chartutil.RequirementsLock{ - Dependencies: []*chartutil.Dependency{ + expect: &chart.RequirementsLock{ + Dependencies: []*chart.Dependency{ {Name: "alpine", Repository: "http://example.com", Version: "0.2.0"}, }, }, }, { name: "repo from valid local path", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, }, }, - expect: &chartutil.RequirementsLock{ - Dependencies: []*chartutil.Dependency{ + expect: &chart.RequirementsLock{ + Dependencies: []*chart.Dependency{ {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, }, }, }, { name: "repo from invalid local path", - req: &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req: &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "notexist", Repository: "file://../testdata/notexist", Version: "0.1.0"}, }, }, @@ -147,8 +147,8 @@ func TestResolve(t *testing.T) { func TestHashReq(t *testing.T) { expect := "sha256:e70e41f8922e19558a8bf62f591a8b70c8e4622e3c03e5415f09aba881f13885" - req := &chartutil.Requirements{ - Dependencies: []*chartutil.Dependency{ + req := &chart.Requirements{ + Dependencies: []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, }, } @@ -160,7 +160,7 @@ func TestHashReq(t *testing.T) { t.Errorf("Expected %q, got %q", expect, h) } - req = &chartutil.Requirements{Dependencies: []*chartutil.Dependency{}} + req = &chart.Requirements{Dependencies: []*chart.Dependency{}} h, err = HashReq(req) if err != nil { t.Fatal(err) diff --git a/pkg/tiller/engine.go b/pkg/tiller/engine.go new file mode 100644 index 000000000..06e27c53f --- /dev/null +++ b/pkg/tiller/engine.go @@ -0,0 +1,40 @@ +/* +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 tiller + +import ( + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chartutil" +) + +// Engine represents a template engine that can render templates. +// +// For some engines, "rendering" includes both compiling and executing. (Other +// engines do not distinguish between phases.) +// +// The engine returns a map where the key is the named output entity (usually +// a file name) and the value is the rendered content of the template. +// +// An Engine must be capable of executing multiple concurrent requests, but +// without tainting one request's environment with data from another request. +type Engine interface { + // Render renders a chart. + // + // It receives a chart, a config, and a map of overrides to the config. + // Overrides are assumed to be passed from the system, not the user. + Render(*chart.Chart, chartutil.Values) (map[string]string, error) +} diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index fb2293789..8815b2d2d 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -29,64 +29,9 @@ import ( "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" - "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/engine" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/kube" ) -// GoTplEngine is the name of the Go template engine, as registered in the EngineYard. -const GoTplEngine = "gotpl" - -// DefaultEngine points to the engine that the EngineYard should treat as the -// default. A chart that does not specify an engine may be run through the -// default engine. -var DefaultEngine = GoTplEngine - -// EngineYard maps engine names to engine implementations. -type EngineYard map[string]Engine - -// Get retrieves a template engine by name. -// -// If no matching template engine is found, the second return value will -// be false. -func (y EngineYard) Get(k string) (Engine, bool) { - e, ok := y[k] - return e, ok -} - -// Default returns the default template engine. -// -// The default is specified by DefaultEngine. -// -// If the default template engine cannot be found, this panics. -func (y EngineYard) Default() Engine { - d, ok := y[DefaultEngine] - if !ok { - // This is a developer error! - panic("Default template engine does not exist") - } - return d -} - -// Engine represents a template engine that can render templates. -// -// For some engines, "rendering" includes both compiling and executing. (Other -// engines do not distinguish between phases.) -// -// The engine returns a map where the key is the named output entity (usually -// a file name) and the value is the rendered content of the template. -// -// An Engine must be capable of executing multiple concurrent requests, but -// without tainting one request's environment with data from another request. -type Engine interface { - // Render renders a chart. - // - // It receives a chart, a config, and a map of overrides to the config. - // Overrides are assumed to be passed from the system, not the user. - Render(*chart.Chart, chartutil.Values) (map[string]string, error) -} - // KubeClient represents a client capable of communicating with the Kubernetes API. // // A KubeClient must be concurrency safe. @@ -193,25 +138,3 @@ func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reade _, err := io.Copy(p.Out, reader) return core.PodUnknown, err } - -// Environment provides the context for executing a client request. -// -// All services in a context are concurrency safe. -type Environment struct { - // EngineYard provides access to the known template engines. - EngineYard EngineYard -} - -// New returns an environment initialized with the defaults. -func New() *Environment { - e := engine.New() - var ey EngineYard = map[string]Engine{ - // Currently, the only template engine we support is the GoTpl one. But - // we can easily add some here. - GoTplEngine: e, - } - - return &Environment{ - EngineYard: ey, - } -} diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index 616163e4c..47299c6b6 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -25,19 +25,9 @@ import ( "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" - "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/kube" ) -type mockEngine struct { - out map[string]string -} - -func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]string, error) { - return e.out, nil -} - type mockKubeClient struct{} func (k *mockKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { @@ -69,25 +59,9 @@ func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader i return "", nil } -var _ Engine = &mockEngine{} var _ KubeClient = &mockKubeClient{} var _ KubeClient = &PrintingKubeClient{} -func TestEngine(t *testing.T) { - eng := &mockEngine{out: map[string]string{"albatross": "test"}} - - env := New() - env.EngineYard = EngineYard(map[string]Engine{"test": eng}) - - if engine, ok := env.EngineYard.Get("test"); !ok { - t.Errorf("failed to get engine from EngineYard") - } else if out, err := engine.Render(&chart.Chart{}, map[string]interface{}{}); err != nil { - t.Errorf("unexpected template error: %s", err) - } else if out["albatross"] != "test" { - t.Errorf("expected 'test', got %q", out["albatross"]) - } -} - func TestKubeClient(t *testing.T) { kc := &mockKubeClient{} diff --git a/pkg/tiller/hook_sorter.go b/pkg/tiller/hook_sorter.go index cc6e7e992..4643dc439 100644 --- a/pkg/tiller/hook_sorter.go +++ b/pkg/tiller/hook_sorter.go @@ -17,37 +17,16 @@ limitations under the License. package tiller import ( - "sort" - "k8s.io/helm/pkg/hapi/release" ) -// sortByHookWeight does an in-place sort of hooks by their supplied weight. -func sortByHookWeight(hooks []*release.Hook) []*release.Hook { - hs := newHookWeightSorter(hooks) - sort.Sort(hs) - return hs.hooks -} - -type hookWeightSorter struct { - hooks []*release.Hook -} - -func newHookWeightSorter(h []*release.Hook) *hookWeightSorter { - return &hookWeightSorter{ - hooks: h, - } -} - -func (hs *hookWeightSorter) Len() int { return len(hs.hooks) } - -func (hs *hookWeightSorter) Swap(i, j int) { - hs.hooks[i], hs.hooks[j] = hs.hooks[j], hs.hooks[i] -} +type hookByWeight []*release.Hook -func (hs *hookWeightSorter) Less(i, j int) bool { - if hs.hooks[i].Weight == hs.hooks[j].Weight { - return hs.hooks[i].Name < hs.hooks[j].Name +func (x hookByWeight) Len() int { return len(x) } +func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x hookByWeight) Less(i, j int) bool { + if x[i].Weight == x[j].Weight { + return x[i].Name < x[j].Name } - return hs.hooks[i].Weight < hs.hooks[j].Weight + return x[i].Weight < x[j].Weight } diff --git a/pkg/tiller/hook_sorter_test.go b/pkg/tiller/hook_sorter_test.go index 4e33bdad4..3360fcbd1 100644 --- a/pkg/tiller/hook_sorter_test.go +++ b/pkg/tiller/hook_sorter_test.go @@ -17,6 +17,7 @@ limitations under the License. package tiller import ( + "sort" "testing" "k8s.io/helm/pkg/hapi/release" @@ -61,10 +62,10 @@ func TestHookSorter(t *testing.T) { }, } - res := sortByHookWeight(hooks) + sort.Sort(hookByWeight(hooks)) got := "" expect := "abcdefg" - for _, r := range res { + for _, r := range hooks { got += r.Name } if got != expect { diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 29c311509..55e748a66 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -78,7 +78,7 @@ type manifestFile struct { // // Files that do not parse into the expected format are simply placed into a map and // returned. -func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) { +func SortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) { result := &result{} for filePath, c := range files { diff --git a/pkg/tiller/hooks_test.go b/pkg/tiller/hooks_test.go index 694c1cab1..ef0fdbd40 100644 --- a/pkg/tiller/hooks_test.go +++ b/pkg/tiller/hooks_test.go @@ -140,7 +140,7 @@ metadata: manifests[o.path] = o.manifest } - hs, generic, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder) + hs, generic, err := SortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder) if err != nil { t.Fatalf("Unexpected error: %s", err) } diff --git a/pkg/tiller/release_content_test.go b/pkg/tiller/release_content_test.go index b5947fc57..71b5a3e02 100644 --- a/pkg/tiller/release_content_test.go +++ b/pkg/tiller/release_content_test.go @@ -34,7 +34,7 @@ func TestGetReleaseContent(t *testing.T) { t.Errorf("Error getting release content: %s", err) } - if res.Chart.Metadata.Name != rel.Chart.Metadata.Name { - t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Chart.Metadata.Name) + if res.Chart.Name() != rel.Chart.Name() { + t.Errorf("Expected %q, got %q", rel.Chart.Name(), res.Chart.Name()) } } diff --git a/pkg/tiller/release_install.go b/pkg/tiller/release_install.go index db07249f5..c9087ffd7 100644 --- a/pkg/tiller/release_install.go +++ b/pkg/tiller/release_install.go @@ -66,16 +66,16 @@ func (s *ReleaseServer) prepareRelease(req *hapi.InstallReleaseRequest) (*releas } revision := 1 - ts := time.Now() options := chartutil.ReleaseOptions{ Name: name, IsInstall: true, } - valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) + valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options, caps) if err != nil { return nil, err } + ts := time.Now() hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) if err != nil { // Return a release with partial data so that client can show debugging diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index a6201489e..511babb71 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -20,6 +20,7 @@ import ( "bytes" "path" "regexp" + "sort" "strings" "time" @@ -29,9 +30,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/discovery" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hooks" relutil "k8s.io/helm/pkg/releaseutil" @@ -78,7 +80,7 @@ var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+ // ReleaseServer implements the server-side gRPC endpoint for the HAPI services. type ReleaseServer struct { - env *environment.Environment + engine Engine discovery discovery.DiscoveryInterface // Releases stores records of releases. @@ -90,9 +92,9 @@ type ReleaseServer struct { } // NewReleaseServer creates a new release server. -func NewReleaseServer(env *environment.Environment, discovery discovery.DiscoveryInterface, kubeClient environment.KubeClient) *ReleaseServer { +func NewReleaseServer(discovery discovery.DiscoveryInterface, kubeClient environment.KubeClient) *ReleaseServer { return &ReleaseServer{ - env: env, + engine: engine.New(), discovery: discovery, Releases: storage.Init(driver.NewMemory()), KubeClient: kubeClient, @@ -204,18 +206,6 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { return "ERROR", errors.New("no available release name found") } -func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { - renderer := s.env.EngineYard.Default() - if ch.Metadata.Engine != "" { - if r, ok := s.env.EngineYard.Get(ch.Metadata.Engine); ok { - renderer = r - } else { - s.Log("warning: %s requested non-existent template engine %s", ch.Metadata.Name, ch.Metadata.Engine) - } - } - return renderer -} - // capabilities builds a Capabilities from discovery information. func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, error) { sv, err := disc.ServerVersion() @@ -269,9 +259,8 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values } } - s.Log("rendering %s chart using values", ch.Metadata.Name) - renderer := s.engine(ch) - files, err := renderer.Render(ch, values) + s.Log("rendering %s chart using values", ch.Name()) + files, err := s.engine.Render(ch, values) if err != nil { return nil, nil, "", err } @@ -286,7 +275,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values if strings.HasSuffix(k, notesFileSuffix) { // Only apply the notes if it belongs to the parent chart // Note: Do not use filePath.Join since it creates a path with \ which is not expected - if k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix) { + if k == path.Join(ch.Name(), "templates", notesFileSuffix) { notes = v } delete(files, k) @@ -296,7 +285,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values // Sort hooks, manifests, and partials. Only hooks and manifests are returned, // as partials are not used after renderer.Render. Empty manifests are also // removed here. - hooks, manifests, err := sortManifests(files, vs, InstallOrder) + hooks, manifests, err := SortManifests(files, vs, InstallOrder) if err != nil { // By catching parse errors here, we can prevent bogus releases from going // to Kubernetes. @@ -351,7 +340,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin } } - executingHooks = sortByHookWeight(executingHooks) + sort.Sort(hookByWeight(executingHooks)) for _, h := range executingHooks { if err := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.BeforeHookCreation, name, namespace, hook, s.KubeClient); err != nil { diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index 9840c4731..97b4b19f9 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -32,8 +32,9 @@ import ( "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/kube" @@ -93,10 +94,9 @@ data: func rsFixture(t *testing.T) *ReleaseServer { t.Helper() - env := environment.New() dc := fake.NewSimpleClientset().Discovery() kc := &environment.PrintingKubeClient{Out: ioutil.Discard} - rs := NewReleaseServer(env, dc, kc) + rs := NewReleaseServer(dc, kc) rs.Log = func(format string, v ...interface{}) { t.Helper() if *verbose { @@ -142,7 +142,7 @@ func withKube(version string) chartOption { func withDependency(dependencyOpts ...chartOption) chartOption { return func(opts *chartOptions) { - opts.Dependencies = append(opts.Dependencies, buildChart(dependencyOpts...)) + opts.AddDependency(buildChart(dependencyOpts...)) } } @@ -483,26 +483,23 @@ func (kc *mockHooksKubeClient) WatchUntilReady(ns string, r io.Reader, timeout i return nil } -func (kc *mockHooksKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force, recreate bool, timeout int64, shouldWait bool) error { +func (kc *mockHooksKubeClient) Update(_ string, _, _ io.Reader, _, _ bool, _ int64, _ bool) error { return nil } -func (kc *mockHooksKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { +func (kc *mockHooksKubeClient) Build(_ string, _ io.Reader) (kube.Result, error) { return []*resource.Info{}, nil } -func (kc *mockHooksKubeClient) BuildUnstructured(ns string, reader io.Reader) (kube.Result, error) { +func (kc *mockHooksKubeClient) BuildUnstructured(_ string, _ io.Reader) (kube.Result, error) { return []*resource.Info{}, nil } -func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { +func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(_ string, _ io.Reader, _ time.Duration) (core.PodPhase, error) { return core.PodUnknown, nil } func deletePolicyStub(kubeClient *mockHooksKubeClient) *ReleaseServer { - e := environment.New() - - dc := fake.NewSimpleClientset().Discovery() return &ReleaseServer{ - env: e, - discovery: dc, + engine: engine.New(), + discovery: fake.NewSimpleClientset().Discovery(), KubeClient: kubeClient, Log: func(_ string, _ ...interface{}) {}, } diff --git a/pkg/tiller/release_uninstall.go b/pkg/tiller/release_uninstall.go index 9abb7559c..b39e9a2b6 100644 --- a/pkg/tiller/release_uninstall.go +++ b/pkg/tiller/release_uninstall.go @@ -132,7 +132,7 @@ func (s *ReleaseServer) deleteRelease(rel *release.Release) (kept string, errs [ } manifests := relutil.SplitManifests(rel.Manifest) - _, files, err := sortManifests(manifests, vs, UninstallOrder) + _, files, err := SortManifests(manifests, vs, UninstallOrder) if err != nil { // We could instead just delete everything in no particular order. // FIXME: One way to delete at this point would be to try a label-based diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go index 763c49bfb..2d86b65b0 100644 --- a/pkg/tiller/release_update.go +++ b/pkg/tiller/release_update.go @@ -105,7 +105,7 @@ func (s *ReleaseServer) prepareUpdate(req *hapi.UpdateReleaseRequest) (*release. if err != nil { return nil, nil, err } - valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) + valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options, caps) if err != nil { return nil, nil, err } diff --git a/pkg/tiller/release_update_test.go b/pkg/tiller/release_update_test.go index 08a119ff2..15c367311 100644 --- a/pkg/tiller/release_update_test.go +++ b/pkg/tiller/release_update_test.go @@ -22,8 +22,8 @@ import ( "strings" "testing" + "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/hapi" - "k8s.io/helm/pkg/hapi/chart" "k8s.io/helm/pkg/hapi/release" )