Merge pull request #4358 from novas0x2a/fake-improvements

Flesh out the fake client even more
pull/4461/head
Matthew Fisher 6 years ago committed by GitHub
commit faa921dd7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

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

@ -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"
)
@ -238,7 +239,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 {

@ -17,23 +17,29 @@ limitations under the License.
package helm // import "k8s.io/helm/pkg/helm"
import (
"bytes"
"errors"
"math/rand"
"strings"
"sync"
"github.com/golang/protobuf/ptypes/timestamp"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
rls "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/proto/hapi/version"
"k8s.io/helm/pkg/renderutil"
storage "k8s.io/helm/pkg/storage/driver"
)
// FakeClient implements Interface
type FakeClient struct {
Rels []*release.Release
Responses map[string]release.TestRun_Status
Opts options
Rels []*release.Release
Responses map[string]release.TestRun_Status
Opts options
RenderManifests bool
}
// Option returns the fake release client
@ -96,7 +102,22 @@ func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts
return nil, errors.New("cannot re-use a name that is still in use")
}
release := ReleaseMock(&MockReleaseOptions{Name: releaseName, Namespace: ns, Description: releaseDescription})
mockOpts := &MockReleaseOptions{
Name: releaseName,
Chart: chart,
Config: c.Opts.instReq.Values,
Namespace: ns,
Description: releaseDescription,
}
release := ReleaseMock(mockOpts)
if c.RenderManifests {
if err := RenderReleaseMock(release, false); err != nil {
return nil, err
}
}
if !c.Opts.dryRun {
c.Rels = append(c.Rels, release)
}
@ -135,14 +156,44 @@ func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateO
}
// UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists
func (c *FakeClient) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
func (c *FakeClient) UpdateReleaseFromChart(rlsName string, newChart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
for _, opt := range opts {
opt(&c.Opts)
}
// Check to see if the release already exists.
rel, err := c.ReleaseContent(rlsName, nil)
if err != nil {
return nil, err
}
return &rls.UpdateReleaseResponse{Release: rel.Release}, nil
mockOpts := &MockReleaseOptions{
Name: rel.Release.Name,
Version: rel.Release.Version + 1,
Chart: newChart,
Config: c.Opts.updateReq.Values,
Namespace: rel.Release.Namespace,
Description: c.Opts.updateReq.Description,
}
newRelease := ReleaseMock(mockOpts)
if c.Opts.updateReq.ResetValues {
newRelease.Config = &chart.Config{Raw: "{}"}
} else if c.Opts.updateReq.ReuseValues {
// TODO: This should merge old and new values but does not.
}
if c.RenderManifests {
if err := RenderReleaseMock(newRelease, true); err != nil {
return nil, err
}
}
if !c.Opts.dryRun {
*rel.Release = *newRelease
}
return &rls.UpdateReleaseResponse{Release: newRelease}, nil
}
// RollbackRelease returns nil, nil
@ -231,12 +282,15 @@ type MockReleaseOptions struct {
Name string
Version int32
Chart *chart.Chart
Config *chart.Config
StatusCode release.Status_Code
Namespace string
Description string
}
// ReleaseMock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing.
// ReleaseMock creates a mock release object based on options set by
// MockReleaseOptions. This function should typically not be used outside of
// testing.
func ReleaseMock(opts *MockReleaseOptions) *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
@ -273,6 +327,11 @@ func ReleaseMock(opts *MockReleaseOptions) *release.Release {
}
}
config := opts.Config
if config == nil {
config = &chart.Config{Raw: `name: "value"`}
}
scode := release.Status_DEPLOYED
if opts.StatusCode > 0 {
scode = opts.StatusCode
@ -287,7 +346,7 @@ func ReleaseMock(opts *MockReleaseOptions) *release.Release {
Description: description,
},
Chart: ch,
Config: &chart.Config{Raw: `name: "value"`},
Config: config,
Version: version,
Namespace: namespace,
Hooks: []*release.Hook{
@ -303,3 +362,39 @@ func ReleaseMock(opts *MockReleaseOptions) *release.Release {
Manifest: MockManifest,
}
}
// RenderReleaseMock will take a release (usually produced by helm.ReleaseMock)
// and will render the Manifest inside using the local mechanism (no tiller).
// (Compare to renderResources in pkg/tiller)
func RenderReleaseMock(r *release.Release, asUpgrade bool) error {
if r == nil || r.Chart == nil || r.Chart.Metadata == nil {
return errors.New("a release with a chart with metadata must be provided to render the manifests")
}
renderOpts := renderutil.Options{
ReleaseOptions: chartutil.ReleaseOptions{
Name: r.Name,
Namespace: r.Namespace,
Time: r.Info.LastDeployed,
Revision: int(r.Version),
IsUpgrade: asUpgrade,
IsInstall: !asUpgrade,
},
}
rendered, err := renderutil.Render(r.Chart, r.Config, renderOpts)
if err != nil {
return err
}
b := bytes.NewBuffer(nil)
for _, m := range manifest.SplitManifests(rendered) {
// Remove empty manifests
if len(strings.TrimSpace(m.Content)) == 0 {
continue
}
b.WriteString("\n---\n# Source: " + m.Name + "\n")
b.WriteString(m.Content)
}
r.Manifest = b.String()
return nil
}

@ -17,6 +17,7 @@ limitations under the License.
package helm
import (
"fmt"
"reflect"
"testing"
@ -25,6 +26,57 @@ import (
rls "k8s.io/helm/pkg/proto/hapi/services"
)
const cmInputTemplate = `kind: ConfigMap
apiVersion: v1
metadata:
name: example
data:
Release:
{{.Release | toYaml | indent 4}}
`
const cmOutputTemplate = `
---
# Source: installChart/templates/cm.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: example
data:
Release:
IsInstall: %t
IsUpgrade: %t
Name: new-release
Namespace: default
Revision: %d
Service: Tiller
Time:
seconds: 242085845
`
var installChart *chart.Chart
func init() {
installChart = &chart.Chart{
Metadata: &chart.Metadata{Name: "installChart"},
Templates: []*chart.Template{
{Name: "templates/cm.yaml", Data: []byte(cmInputTemplate)},
},
}
}
func releaseWithChart(opts *MockReleaseOptions) *release.Release {
if opts.Chart == nil {
opts.Chart = installChart
}
return ReleaseMock(opts)
}
func withManifest(r *release.Release, isUpgrade bool) *release.Release {
r.Manifest = fmt.Sprintf(cmOutputTemplate, !isUpgrade, isUpgrade, r.Version)
return r
}
func TestFakeClient_ReleaseStatus(t *testing.T) {
releasePresent := ReleaseMock(&MockReleaseOptions{Name: "release-present"})
releaseNotPresent := ReleaseMock(&MockReleaseOptions{Name: "release-not-present"})
@ -117,9 +169,9 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
}
func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
installChart := &chart.Chart{}
type fields struct {
Rels []*release.Release
Rels []*release.Release
RenderManifests bool
}
type args struct {
ns string
@ -143,10 +195,10 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
opts: []InstallOption{ReleaseName("new-release")},
},
want: &rls.InstallReleaseResponse{
Release: ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
Release: releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
wantErr: false,
},
@ -160,10 +212,10 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
opts: []InstallOption{ReleaseName("new-release"), InstallDescription("foo-bar")},
},
want: &rls.InstallReleaseResponse{
Release: ReleaseMock(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}),
Release: releaseWithChart(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}),
},
relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}),
releaseWithChart(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}),
},
wantErr: false,
},
@ -171,7 +223,7 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
name: "Try to add a release where the name already exists.",
fields: fields{
Rels: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
},
args: args{
@ -179,16 +231,35 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
opts: []InstallOption{ReleaseName("new-release")},
},
relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
want: nil,
wantErr: true,
},
{
name: "Render the given chart.",
fields: fields{
Rels: []*release.Release{},
RenderManifests: true,
},
args: args{
ns: "default",
opts: []InstallOption{ReleaseName("new-release")},
},
want: &rls.InstallReleaseResponse{
Release: withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release"}), false),
},
relsAfter: []*release.Release{
withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release"}), false),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &FakeClient{
Rels: tt.fields.Rels,
Rels: tt.fields.Rels,
RenderManifests: tt.fields.RenderManifests,
}
got, err := c.InstallReleaseFromChart(installChart, tt.args.ns, tt.args.opts...)
if (err != nil) != tt.wantErr {
@ -293,7 +364,84 @@ func TestFakeClient_DeleteRelease(t *testing.T) {
}
if !reflect.DeepEqual(c.Rels, tt.relsAfter) {
t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", got, tt.relsAfter)
t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", c.Rels, tt.relsAfter)
}
})
}
}
func TestFakeClient_UpdateReleaseFromChart(t *testing.T) {
type fields struct {
Rels []*release.Release
RenderManifests bool
}
type args struct {
release string
opts []UpdateOption
}
tests := []struct {
name string
fields fields
args args
want *rls.UpdateReleaseResponse
relsAfter []*release.Release
wantErr bool
}{
{
name: "Update release.",
fields: fields{
Rels: []*release.Release{
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
},
args: args{
release: "new-release",
opts: []UpdateOption{},
},
want: &rls.UpdateReleaseResponse{
Release: releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}),
},
relsAfter: []*release.Release{
releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}),
},
},
{
name: "Update and render given chart.",
fields: fields{
Rels: []*release.Release{
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
RenderManifests: true,
},
args: args{
release: "new-release",
opts: []UpdateOption{},
},
want: &rls.UpdateReleaseResponse{
Release: withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}), true),
},
relsAfter: []*release.Release{
withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}), true),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &FakeClient{
Rels: tt.fields.Rels,
RenderManifests: tt.fields.RenderManifests,
}
got, err := c.UpdateReleaseFromChart(tt.args.release, installChart, tt.args.opts...)
if (err != nil) != tt.wantErr {
t.Errorf("FakeClient.UpdateReleaseFromChart() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FakeClient.UpdateReleaseFromChart() =\n%v\nwant\n%v", got, tt.want)
}
if !reflect.DeepEqual(c.Rels, tt.relsAfter) {
t.Errorf("FakeClient.UpdateReleaseFromChart() rels =\n%v\nwant\n%v", c.Rels, tt.relsAfter)
}
})
}

@ -0,0 +1,23 @@
/*
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 contains tools for working with kubernetes manifests.
Much like other parts of helm, it does not generally require that the manifests
be correct yaml, so these functions can be run on broken manifests to aid in
user debugging
*/
package manifest // import "k8s.io/helm/pkg/manifest"

@ -0,0 +1,46 @@
/*
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"
)
var (
kindRegex = regexp.MustCompile("kind:(.*)\n")
)
// 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
for k, v := range templates {
match := kindRegex.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
}

@ -0,0 +1,28 @@
/*
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 (
"k8s.io/helm/pkg/releaseutil"
)
// Manifest represents a manifest file, which has a name and some content.
type Manifest struct {
Name string
Content string
Head *releaseutil.SimpleHead
}

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

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

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

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

@ -27,6 +27,7 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
)
@ -52,12 +53,7 @@ var deletePolices = map[string]release.Hook_DeletePolicy{
hooks.BeforeHookCreation: release.Hook_BEFORE_HOOK_CREATION,
}
// Manifest represents a manifest file, which has a name and some content.
type Manifest struct {
Name string
Content string
Head *util.SimpleHead
}
type Manifest = manifest.Manifest
type result struct {
hooks []*release.Hook

Loading…
Cancel
Save