ref(*): refactor chart/chartutil

ref(chartutil): move chart loading out of chartutil into new package
    add chart loader interface to allow lazy loading
feat(chart): create chart accessors
ref(*): cleanup requirements
ref(tiller): remove optional template engines
ref(tiller): simplify sorting releases and hooks
ref(*): code simplification
ref(hapi): move chart package out of hapi
ref(chart): add requirements and lock to Chart struct
pull/4518/head
Adam Reese 6 years ago
parent 56c4b9b48d
commit f012940d9c
No known key found for this signature in database
GPG Key ID: 06F35E60A7A18DD6

@ -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 != "" {

@ -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)
}

@ -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
}

@ -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

@ -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)
}

@ -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
}

@ -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 {

@ -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

@ -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
}

@ -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)

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

@ -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
}

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

@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra"
"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,

@ -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)
}

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

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

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

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

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

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

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

@ -1,11 +1,10 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Copyright 2018 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
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"

@ -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
}

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

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

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

@ -14,27 +14,35 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
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())
}
}
}

Binary file not shown.

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

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

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

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

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

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

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

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

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

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

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

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

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

After

Width:  |  Height:  |  Size: 374 B

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

After

Width:  |  Height:  |  Size: 374 B

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

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

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

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

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

@ -20,13 +20,14 @@ import (
"runtime"
"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
}

@ -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) {

@ -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 {

@ -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 {

@ -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), "<CHARTNAME>", 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), "<CHARTNAME>", 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, "<CHARTNAME>", chartfile.Name),
content: transform(defaultIngress, chartfile.Name),
},
{
// deployment.yaml
path: filepath.Join(cdir, TemplatesDir, DeploymentName),
content: Transform(defaultDeployment, "<CHARTNAME>", chartfile.Name),
content: transform(defaultDeployment, chartfile.Name),
},
{
// service.yaml
path: filepath.Join(cdir, TemplatesDir, ServiceName),
content: Transform(defaultService, "<CHARTNAME>", chartfile.Name),
content: transform(defaultService, chartfile.Name),
},
{
// NOTES.txt
path: filepath.Join(cdir, TemplatesDir, NotesName),
content: Transform(defaultNotes, "<CHARTNAME>", chartfile.Name),
content: transform(defaultNotes, chartfile.Name),
},
{
// _helpers.tpl
path: filepath.Join(cdir, TemplatesDir, HelpersName),
content: Transform(defaultHelpers, "<CHARTNAME>", chartfile.Name),
content: transform(defaultHelpers, chartfile.Name),
},
}
@ -413,3 +415,9 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) {
}
return cdir, nil
}
// transform performs a string replacement of the specified source for
// a given key with the replacement string
func transform(src, replacement string) []byte {
return []byte(strings.Replace(src, "<CHARTNAME>", replacement, -1))
}

@ -23,7 +23,8 @@ import (
"strings"
"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() {

@ -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"

@ -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
}

@ -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()

@ -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")
}

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

@ -18,209 +18,88 @@ package chartutil
import (
"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)
}

@ -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)
}
}
}

@ -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
}

@ -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")

@ -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{}{

@ -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)
}

@ -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
}

@ -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"},

@ -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 "<no value>" 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(), "<no value>", "", -1)
buf.Reset()
f := &chart.File{
Name: strings.Replace(file, "/templates", "/manifests", -1),
Data: []byte(strings.Replace(buf.String(), "<no value>", "", -1)),
}
rendered[file] = string(f.Data)
if ch != nil {
ch.Files = append(ch.Files, f)
}
}
return rendered, nil
@ -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),

@ -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)
}
}

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

@ -15,7 +15,7 @@ limitations under the License.
package release
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.

@ -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"
)

@ -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
}

@ -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"
)

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

Loading…
Cancel
Save