diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 1488b87c5..1c2dca7ef 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -37,8 +37,8 @@ import ( "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/kube" - "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/renderutil" "k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/strvals" ) @@ -254,7 +254,7 @@ func (i *installCmd) run() error { // 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 - if err := checkDependencies(chartRequested, req); err != nil { + if err := renderutil.CheckDependencies(chartRequested, req); err != nil { if i.depUp { man := &downloader.Manager{ Out: i.out, @@ -515,29 +515,6 @@ func defaultNamespace() string { return "default" } -func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { - missing := []string{} - - deps := ch.GetDependencies() - for _, r := range reqs.Dependencies { - found := false - for _, d := range deps { - if d.Metadata.Name == r.Name { - found = true - break - } - } - if !found { - missing = append(missing, r.Name) - } - } - - if len(missing) > 0 { - return fmt.Errorf("found in requirements.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) - } - return nil -} - //readFile load a file from the local directory or a remote file with a url. func readFile(filePath, CertFile, KeyFile, CAFile string) ([]byte, error) { u, _ := url.Parse(filePath) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 81603e67b..51686dba7 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -35,6 +35,7 @@ import ( "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/renderutil" "k8s.io/helm/pkg/repo" ) @@ -151,7 +152,7 @@ func (p *packageCmd) run() error { } if reqs, err := chartutil.LoadRequirements(ch); err == nil { - if err := checkDependencies(ch, reqs); err != nil { + if err := renderutil.CheckDependencies(ch, reqs); err != nil { return err } } else { diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 54eb9ff49..63609c18c 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -27,17 +27,15 @@ import ( "strings" "time" - "github.com/Masterminds/semver" "github.com/spf13/cobra" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/manifest" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" - util "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/renderutil" "k8s.io/helm/pkg/tiller" "k8s.io/helm/pkg/timeconv" - tversion "k8s.io/helm/pkg/version" ) const defaultDirectoryPermission = 0755 @@ -154,69 +152,21 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error { return prettyError(err) } - if req, err := chartutil.LoadRequirements(c); err == nil { - if err := checkDependencies(c, req); err != nil { - return prettyError(err) - } - } else if err != chartutil.ErrRequirementsNotFound { - return fmt.Errorf("cannot load requirements: %v", err) - } - options := chartutil.ReleaseOptions{ - Name: t.releaseName, - IsInstall: !t.releaseIsUpgrade, - IsUpgrade: t.releaseIsUpgrade, - Time: timeconv.Now(), - Namespace: t.namespace, - } - - err = chartutil.ProcessRequirementsEnabled(c, config) - if err != nil { - return err - } - err = chartutil.ProcessRequirementsImportValues(c) - if err != nil { - return err - } - - // Set up engine. - renderer := engine.New() - - caps := &chartutil.Capabilities{ - APIVersions: chartutil.DefaultVersionSet, - KubeVersion: chartutil.DefaultKubeVersion, - TillerVersion: tversion.GetVersionProto(), - } - - // kubernetes version - kv, err := semver.NewVersion(t.kubeVersion) - if err != nil { - return fmt.Errorf("could not parse a kubernetes version: %v", err) - } - 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) - if err != nil { - return err + renderOpts := renderutil.Options{ + ReleaseOptions: chartutil.ReleaseOptions{ + Name: t.releaseName, + IsInstall: !t.releaseIsUpgrade, + IsUpgrade: t.releaseIsUpgrade, + Time: timeconv.Now(), + Namespace: t.namespace, + }, + KubeVersion: t.kubeVersion, } - out, err := renderer.Render(c, vals) - listManifests := []tiller.Manifest{} + renderedTemplates, err := renderutil.Render(c, config, renderOpts) if err != nil { return err } - // extract kind and name - re := regexp.MustCompile("kind:(.*)\n") - for k, v := range out { - match := re.FindStringSubmatch(v) - h := "Unknown" - if len(match) == 2 { - h = strings.TrimSpace(match[1]) - } - m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}} - listManifests = append(listManifests, m) - } if settings.Debug { rel := &release.Release{ @@ -230,7 +180,8 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error { printRelease(os.Stdout, rel) } - var manifestsToRender []tiller.Manifest + listManifests := manifest.SplitManifests(renderedTemplates) + var manifestsToRender []manifest.Manifest // if we have a list of files to render, then check that each of the // provided files exists in the chart. diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index ba0649e39..52c6d4fae 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -25,6 +25,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/renderutil" "k8s.io/helm/pkg/storage/driver" ) @@ -211,7 +212,7 @@ func (u *upgradeCmd) run() 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 { + if err := renderutil.CheckDependencies(ch, req); err != nil { return err } } else if err != chartutil.ErrRequirementsNotFound { diff --git a/pkg/manifest/splitter.go b/pkg/manifest/splitter.go new file mode 100644 index 000000000..25b77326d --- /dev/null +++ b/pkg/manifest/splitter.go @@ -0,0 +1,43 @@ +/* +Copyright The Helm Authors. + +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 manifest + +import ( + "regexp" + "strings" + + "k8s.io/helm/pkg/releaseutil" +) + +// SplitManifests takes a map of rendered templates and splits them into the +// detected manifests. +func SplitManifests(templates map[string]string) []Manifest { + var listManifests []Manifest + // extract kind and name + re := regexp.MustCompile("kind:(.*)\n") + for k, v := range templates { + match := re.FindStringSubmatch(v) + h := "Unknown" + if len(match) == 2 { + h = strings.TrimSpace(match[1]) + } + m := Manifest{Name: k, Content: v, Head: &releaseutil.SimpleHead{Kind: h}} + listManifests = append(listManifests, m) + } + + return listManifests +} diff --git a/pkg/renderutil/deps.go b/pkg/renderutil/deps.go new file mode 100644 index 000000000..72e4d12a1 --- /dev/null +++ b/pkg/renderutil/deps.go @@ -0,0 +1,50 @@ +/* +Copyright The Helm Authors. + +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 renderutil + +import ( + "fmt" + "strings" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// CheckDependencies will do a simple dependency check on the chart for local +// rendering +func CheckDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { + missing := []string{} + + deps := ch.GetDependencies() + for _, r := range reqs.Dependencies { + found := false + for _, d := range deps { + if d.Metadata.Name == r.Name { + found = true + break + } + } + if !found { + missing = append(missing, r.Name) + } + } + + if len(missing) > 0 { + return fmt.Errorf("found in requirements.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) + } + return nil +} diff --git a/pkg/renderutil/doc.go b/pkg/renderutil/doc.go new file mode 100644 index 000000000..38c3ae60d --- /dev/null +++ b/pkg/renderutil/doc.go @@ -0,0 +1,24 @@ +/* +Copyright The Helm Authors. + +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 renderutil contains tools related to the local rendering of charts. + +Local rendering means rendering without the tiller; this is generally used for +local debugging and testing (see the `helm template` command for examples of +use). This package will not render charts exactly the same way as the tiller +will, but will be generally close enough for local debug purposes. +*/ +package renderutil // import "k8s.io/helm/pkg/renderutil" diff --git a/pkg/renderutil/render.go b/pkg/renderutil/render.go new file mode 100644 index 000000000..1996e1dc2 --- /dev/null +++ b/pkg/renderutil/render.go @@ -0,0 +1,88 @@ +/* +Copyright The Helm Authors. + +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 renderutil + +import ( + "fmt" + + "github.com/Masterminds/semver" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/proto/hapi/chart" + tversion "k8s.io/helm/pkg/version" +) + +// Options are options for this simple local render +type Options struct { + ReleaseOptions chartutil.ReleaseOptions + KubeVersion string +} + +// Render chart templates locally and display the output. +// This does not require the Tiller. Any values that would normally be +// looked up or retrieved in-cluster will be faked locally. Additionally, none +// of the server-side testing of chart validity (e.g. whether an API is supported) +// is done. +// +// Note: a `nil` config passed here means "ignore the chart's default values"; +// if you want the normal behavior of merging the defaults with the new config, +// you should pass `&chart.Config{Raw: "{}"}, +func Render(c *chart.Chart, config *chart.Config, opts Options) (map[string]string, error) { + if req, err := chartutil.LoadRequirements(c); err == nil { + if err := CheckDependencies(c, req); err != nil { + return nil, err + } + } else if err != chartutil.ErrRequirementsNotFound { + return nil, fmt.Errorf("cannot load requirements: %v", err) + } + + err := chartutil.ProcessRequirementsEnabled(c, config) + if err != nil { + return nil, err + } + err = chartutil.ProcessRequirementsImportValues(c) + if err != nil { + return nil, err + } + + // Set up engine. + renderer := engine.New() + + caps := &chartutil.Capabilities{ + APIVersions: chartutil.DefaultVersionSet, + KubeVersion: chartutil.DefaultKubeVersion, + TillerVersion: tversion.GetVersionProto(), + } + + if opts.KubeVersion != "" { + kv, verErr := semver.NewVersion(opts.KubeVersion) + if verErr != nil { + return nil, fmt.Errorf("could not parse a kubernetes version: %v", verErr) + } + 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, opts.ReleaseOptions, caps) + if err != nil { + return nil, err + } + + return renderer.Render(c, vals) +} diff --git a/pkg/renderutil/render_test.go b/pkg/renderutil/render_test.go new file mode 100644 index 000000000..e10ff883c --- /dev/null +++ b/pkg/renderutil/render_test.go @@ -0,0 +1,153 @@ +/* +Copyright The Helm Authors. + +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 renderutil + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +const cmTemplate = `kind: ConfigMap +apiVersion: v1 +metadata: + name: example +data: + Chart: +{{.Chart | toYaml | indent 4}} + Release: +{{.Release | toYaml | indent 4}} + Values: +{{.Values | toYaml | indent 4}} +` + +func TestRender(t *testing.T) { + + testChart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/cm.yaml", Data: []byte(cmTemplate)}, + }, + Values: &chart.Config{Raw: "meow: defaultmeow"}, + } + + newConfig := &chart.Config{Raw: "meow: newmeow"} + defaultConfig := &chart.Config{Raw: "{}"} + + tests := map[string]struct { + chart *chart.Chart + config *chart.Config + opts Options + want map[string]string + }{ + "BasicWithValues": { + chart: testChart, + config: newConfig, + opts: Options{}, + want: map[string]string{ + "hello/templates/cm.yaml": `kind: ConfigMap +apiVersion: v1 +metadata: + name: example +data: + Chart: + name: hello + + Release: + IsInstall: false + IsUpgrade: false + Name: "" + Namespace: "" + Revision: 0 + Service: Tiller + Time: null + + Values: + meow: newmeow + +`, + }, + }, + "BasicNoValues": { + chart: testChart, + config: defaultConfig, + opts: Options{}, + want: map[string]string{ + "hello/templates/cm.yaml": `kind: ConfigMap +apiVersion: v1 +metadata: + name: example +data: + Chart: + name: hello + + Release: + IsInstall: false + IsUpgrade: false + Name: "" + Namespace: "" + Revision: 0 + Service: Tiller + Time: null + + Values: + meow: defaultmeow + +`, + }, + }, + "SetSomeReleaseValues": { + chart: testChart, + config: defaultConfig, + opts: Options{ReleaseOptions: chartutil.ReleaseOptions{Name: "meow"}}, + want: map[string]string{ + "hello/templates/cm.yaml": `kind: ConfigMap +apiVersion: v1 +metadata: + name: example +data: + Chart: + name: hello + + Release: + IsInstall: false + IsUpgrade: false + Name: meow + Namespace: "" + Revision: 0 + Service: Tiller + Time: null + + Values: + meow: defaultmeow + +`, + }, + }, + } + + for testName, tt := range tests { + t.Run(testName, func(t *testing.T) { + got, err := Render(tt.chart, tt.config, tt.opts) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +}