Merge pull request #1 from kubernetes/master

Sync with master
pull/1648/head
Nikolay Mahotkin 8 years ago committed by GitHub
commit 23c45dce17

2
.gitignore vendored

@ -1,8 +1,10 @@
.DS_Store
.coverage/
.vimrc
.vscode/
_dist/
_proto/*.pb.go
bin/
rootfs/tiller
vendor/
*.exe

@ -0,0 +1,12 @@
language: go
go:
- '1.7.x'
go_import_path: k8s.io/helm
before_install:
- make bootstrap
script:
- make test

@ -1,6 +1,7 @@
# Kubernetes Helm
[![CircleCI](https://circleci.com/gh/kubernetes/helm.svg?style=svg)](https://circleci.com/gh/kubernetes/helm)
[![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes/helm)](https://goreportcard.com/report/github.com/kubernetes/helm)
Helm is a tool for managing Kubernetes charts. Charts are packages of
pre-configured Kubernetes resources.
@ -32,9 +33,9 @@ Think of it like apt/yum/homebrew for Kubernetes.
Binary downloads of the Helm client can be found at the following links:
- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.0.0-darwin-amd64.tar.gz)
- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.0.0-linux-amd64.tar.gz)
- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.0.0-linux-386.tar.gz)
- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.1.0-darwin-amd64.tar.gz)
- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.1.0-linux-amd64.tar.gz)
- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.1.0-linux-386.tar.gz)
Unpack the `helm` binary and add it to your PATH and you are good to go! OS X/[Cask](https://caskroom.github.io/) users can `brew cask install helm`.

@ -33,6 +33,8 @@ message Status {
SUPERSEDED = 3;
// Status_FAILED indicates that the release was not successfully deployed.
FAILED = 4;
// Status_DELETING indicates that a delete operation is underway.
DELETING = 5;
}
Code code = 1;

@ -256,6 +256,8 @@ message UninstallReleaseRequest {
message UninstallReleaseResponse {
// Release is the release that was marked deleted.
hapi.release.Release release = 1;
// Info is an uninstall message
string info = 2;
}
// GetVersionRequest requests for version information.

@ -3,7 +3,7 @@ machine:
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
environment:
GOVERSION: "1.7.3"
GOVERSION: "1.7.4"
GOPATH: "${HOME}/.go_workspace"
WORKDIR: "${GOPATH}/src/k8s.io/helm"
PROJECT_NAME: "kubernetes-helm"

@ -18,6 +18,7 @@ package main
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
@ -86,6 +87,10 @@ func (d *deleteCmd) run() error {
helm.DeleteDisableHooks(d.disableHooks),
helm.DeletePurge(d.purge),
}
_, err := d.client.DeleteRelease(d.name, opts...)
res, err := d.client.DeleteRelease(d.name, opts...)
if res != nil && res.Info != "" {
fmt.Fprintln(d.out, res.Info)
}
return prettyError(err)
}

@ -44,6 +44,10 @@ const (
// VerifyAlways will always attempt a verification, and will fail if the
// verification fails.
VerifyAlways
// VerifyLater will fetch verification data, but not do any verification.
// This is to accommodate the case where another step of the process will
// perform verification.
VerifyLater
)
// ChartDownloader handles downloading a chart.
@ -65,6 +69,7 @@ type ChartDownloader struct {
// If Verify is set to VerifyNever, the verification will be nil.
// If Verify is set to VerifyIfPossible, this will return a verification (or nil on failure), and print a warning on failure.
// If Verify is set to VerifyAlways, this will return a verification or an error if the verification fails.
// If Verify is set to VerifyLater, this will download the prov file (if it exists), but not verify it.
//
// For VerifyNever and VerifyIfPossible, the Verification may be empty.
//
@ -104,11 +109,13 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, nil, err
}
ver, err = VerifyChart(destfile, c.Keyring)
if err != nil {
// Fail always in this case, since it means the verification step
// failed.
return destfile, ver, err
if c.Verify != VerifyLater {
ver, err = VerifyChart(destfile, c.Keyring)
if err != nil {
// Fail always in this case, since it means the verification step
// failed.
return destfile, ver, err
}
}
}
return destfile, ver, nil

@ -153,3 +153,47 @@ func TestDownloadTo(t *testing.T) {
return
}
}
func TestDownloadTo_VerifyLater(t *testing.T) {
hh, err := ioutil.TempDir("", "helm-downloadto-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(hh)
dest := filepath.Join(hh, "dest")
os.MkdirAll(dest, 0755)
// Set up a fake repo
srv := repotest.NewServer(hh)
defer srv.Stop()
if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil {
t.Error(err)
return
}
c := ChartDownloader{
HelmHome: helmpath.Home("testdata/helmhome"),
Out: os.Stderr,
Verify: VerifyLater,
}
cname := "/signtest-0.1.0.tgz"
where, _, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil {
t.Error(err)
return
}
if expect := filepath.Join(dest, cname); where != expect {
t.Errorf("Expected download to %s, got %s", expect, where)
}
if _, err := os.Stat(filepath.Join(dest, cname)); err != nil {
t.Error(err)
return
}
if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil {
t.Error(err)
return
}
}

@ -51,8 +51,9 @@ type fetchCmd struct {
destdir string
version string
verify bool
keyring string
verify bool
verifyLater bool
keyring string
out io.Writer
}
@ -82,6 +83,7 @@ func newFetchCmd(out io.Writer) *cobra.Command {
f.BoolVar(&fch.untar, "untar", false, "if set to true, will untar the chart after downloading it")
f.StringVar(&fch.untardir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
f.BoolVar(&fch.verify, "verify", false, "verify the package against its signature")
f.BoolVar(&fch.verifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
f.StringVar(&fch.version, "version", "", "specific version of a chart. Without this, the latest version is fetched")
f.StringVar(&fch.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.StringVarP(&fch.destdir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
@ -100,6 +102,8 @@ func (f *fetchCmd) run() error {
if f.verify {
c.Verify = downloader.VerifyAlways
} else if f.verifyLater {
c.Verify = downloader.VerifyLater
}
// If untar is set, we fetch to a tempdir, then untar and copy after
@ -120,7 +124,7 @@ func (f *fetchCmd) run() error {
}
if f.verify {
fmt.Fprintf(f.out, "Verification: %v", v)
fmt.Fprintf(f.out, "Verification: %v\n", v)
}
// After verification, untar the chart into the requested directory.

@ -18,8 +18,10 @@ package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"testing"
"k8s.io/helm/pkg/repo/repotest"
@ -39,13 +41,14 @@ func TestFetchCmd(t *testing.T) {
// all flags will get "--home=TMDIR -d outdir" appended.
tests := []struct {
name string
chart string
flags []string
fail bool
failExpect string
expectFile string
expectDir bool
name string
chart string
flags []string
fail bool
failExpect string
expectFile string
expectDir bool
expectVerify bool
}{
{
name: "Basic chart fetch",
@ -72,10 +75,11 @@ func TestFetchCmd(t *testing.T) {
fail: true,
},
{
name: "Fetch and verify",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"},
expectFile: "./signtest-0.1.0.tgz",
name: "Fetch and verify",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"},
expectFile: "./signtest-0.1.0.tgz",
expectVerify: true,
},
{
name: "Fetch and fail verify",
@ -87,16 +91,17 @@ func TestFetchCmd(t *testing.T) {
{
name: "Fetch and untar",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"},
flags: []string{"--untar", "--untardir", "signtest"},
expectFile: "./signtest",
expectDir: true,
},
{
name: "Fetch, verify, untar",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"},
expectFile: "./signtest",
expectDir: true,
name: "Fetch, verify, untar",
chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"},
expectFile: "./signtest",
expectDir: true,
expectVerify: true,
},
}
@ -126,6 +131,15 @@ func TestFetchCmd(t *testing.T) {
t.Errorf("%q reported error: %s", tt.name, err)
continue
}
if tt.expectVerify {
pointerAddressPattern := "0[xX][A-Fa-f0-9]+"
sha256Pattern := "[A-Fa-f0-9]{64}"
verificationRegex := regexp.MustCompile(
fmt.Sprintf("Verification: &{%s sha256:%s signtest-0.1.0.tgz}\n", pointerAddressPattern, sha256Pattern))
if !verificationRegex.MatchString(buf.String()) {
t.Errorf("%q: expected match for regex %s, got %s", tt.name, verificationRegex, buf.String())
}
}
ef := filepath.Join(outdir, tt.expectFile)
fi, err := os.Stat(ef)

@ -28,18 +28,18 @@ import (
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/tiller/environment"
)
const (
localRepoIndexFilePath = "index.yaml"
homeEnvVar = "HELM_HOME"
hostEnvVar = "HELM_HOST"
tillerNamespace = "kube-system"
)
var (
@ -145,7 +145,7 @@ func main() {
func setupConnection(c *cobra.Command, args []string) error {
if tillerHost == "" {
tunnel, err := newTillerPortForwarder(tillerNamespace, kubeContext)
tunnel, err := newTillerPortForwarder(environment.TillerNamespace, kubeContext)
if err != nil {
return err
}
@ -199,12 +199,12 @@ func homePath() string {
// getKubeClient is a convenience method for creating kubernetes config and client
// for a given kubeconfig context
func getKubeClient(context string) (*restclient.Config, *unversioned.Client, error) {
func getKubeClient(context string) (*restclient.Config, *internalclientset.Clientset, error) {
config, err := kube.GetConfig(context).ClientConfig()
if err != nil {
return nil, nil, fmt.Errorf("could not get kubernetes config for context '%s': %s", context, err)
}
client, err := unversioned.New(config)
client, err := internalclientset.NewForConfig(config)
if err != nil {
return nil, nil, fmt.Errorf("could not get kubernetes client: %s", err)
}

@ -0,0 +1,40 @@
// Copyright 2016 The Kubernetes Authors All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
// +build !windows
package helmpath
import (
"runtime"
"testing"
)
func TestHelmHome(t *testing.T) {
hh := Home("/r")
isEq := func(t *testing.T, a, b string) {
if a != b {
t.Error(runtime.GOOS)
t.Errorf("Expected %q, got %q", a, b)
}
}
isEq(t, hh.String(), "/r")
isEq(t, hh.Repository(), "/r/repository")
isEq(t, hh.RepositoryFile(), "/r/repository/repositories.yaml")
isEq(t, hh.LocalRepository(), "/r/repository/local")
isEq(t, hh.Cache(), "/r/repository/cache")
isEq(t, hh.CacheIndex("t"), "/r/repository/cache/t-index.yaml")
isEq(t, hh.Starters(), "/r/starters")
}

@ -0,0 +1,37 @@
// 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.
// +build windows
package helmpath
import (
"testing"
)
func TestHelmHome(t *testing.T) {
hh := Home("r:\\")
isEq := func(t *testing.T, a, b string) {
if a != b {
t.Errorf("Expected %q, got %q", b, a)
}
}
isEq(t, hh.String(), "r:\\")
isEq(t, hh.Repository(), "r:\\repository")
isEq(t, hh.RepositoryFile(), "r:\\repository\\repositories.yaml")
isEq(t, hh.LocalRepository(), "r:\\repository\\local")
isEq(t, hh.Cache(), "r:\\repository\\cache")
isEq(t, hh.CacheIndex("t"), "r:\\repository\\cache\\t-index.yaml")
isEq(t, hh.Starters(), "r:\\starters")
}

@ -24,11 +24,12 @@ import (
"github.com/spf13/cobra"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned"
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/tiller/environment"
)
const initDesc = `
@ -65,15 +66,17 @@ type initCmd struct {
image string
clientOnly bool
canary bool
namespace string
dryRun bool
out io.Writer
home helmpath.Home
kubeClient unversioned.DeploymentsNamespacer
kubeClient extensionsclient.DeploymentsGetter
}
func newInitCmd(out io.Writer) *cobra.Command {
i := &initCmd{
out: out,
out: out,
namespace: environment.TillerNamespace,
}
cmd := &cobra.Command{
@ -102,7 +105,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
func (i *initCmd) run() error {
if flagDebug {
m, err := installer.DeploymentManifest(i.image, i.canary)
m, err := installer.DeploymentManifest(i.namespace, i.image, i.canary)
if err != nil {
return err
}
@ -124,7 +127,7 @@ func (i *initCmd) run() error {
}
i.kubeClient = c
}
if err := installer.Install(i.kubeClient, tillerNamespace, i.image, i.canary, flagDebug); err != nil {
if err := installer.Install(i.kubeClient, i.namespace, i.image, i.canary, flagDebug); err != nil {
if !kerrors.IsAlreadyExists(err) {
return fmt.Errorf("error installing: %s", err)
}

@ -27,7 +27,9 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
testcore "k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/helm/cmd/helm/helmpath"
@ -41,14 +43,19 @@ func TestInitCmd(t *testing.T) {
defer os.Remove(home)
var buf bytes.Buffer
fake := testclient.Fake{}
cmd := &initCmd{out: &buf, home: helmpath.Home(home), kubeClient: fake.Extensions()}
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc.Extensions(),
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("expected error: %v", err)
}
actions := fake.Actions()
if action, ok := actions[0].(testclient.CreateAction); !ok || action.GetResource() != "deployments" {
t.Errorf("unexpected action: %v, expected create deployment", actions[0])
action := fc.Actions()[0]
if !action.Matches("create", "deployments") {
t.Errorf("unexpected action: %v, expected create deployment", action)
}
expected := "Tiller (the helm server side component) has been installed into your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
@ -64,11 +71,21 @@ func TestInitCmd_exsits(t *testing.T) {
defer os.Remove(home)
var buf bytes.Buffer
fake := testclient.Fake{}
fake.AddReactor("*", "*", func(action testclient.Action) (bool, runtime.Object, error) {
fc := fake.NewSimpleClientset(&extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceDefault,
Name: "tiller-deploy",
},
})
fc.AddReactor("*", "*", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, errors.NewAlreadyExists(api.Resource("deployments"), "1")
})
cmd := &initCmd{out: &buf, home: helmpath.Home(home), kubeClient: fake.Extensions()}
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc.Extensions(),
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("expected error: %v", err)
}
@ -86,12 +103,18 @@ func TestInitCmd_clientOnly(t *testing.T) {
defer os.Remove(home)
var buf bytes.Buffer
fake := testclient.Fake{}
cmd := &initCmd{out: &buf, home: helmpath.Home(home), kubeClient: fake.Extensions(), clientOnly: true}
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc.Extensions(),
clientOnly: true,
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(fake.Actions()) != 0 {
if len(fc.Actions()) != 0 {
t.Error("expected client call")
}
expected := "Not installing tiller due to 'client-only' flag having been set"
@ -114,18 +137,19 @@ func TestInitCmd_dryRun(t *testing.T) {
}()
var buf bytes.Buffer
fake := testclient.Fake{}
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fake.Extensions(),
kubeClient: fc.Extensions(),
clientOnly: true,
dryRun: true,
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Fatal(err)
}
if len(fake.Actions()) != 0 {
if len(fc.Actions()) != 0 {
t.Error("expected no server calls")
}

@ -49,14 +49,14 @@ func TestInspect(t *testing.T) {
}
expect := []string{
strings.TrimSpace(string(cdata)),
strings.TrimSpace(string(data)),
strings.Replace(strings.TrimSpace(string(cdata)), "\r", "", -1),
strings.Replace(strings.TrimSpace(string(data)), "\r", "", -1),
}
// Problem: ghodss/yaml doesn't marshal into struct order. To solve, we
// have to carefully craft the Chart.yaml to match.
for i, got := range parts {
got = strings.TrimSpace(got)
got = strings.Replace(strings.TrimSpace(got), "\r", "", -1)
if got != expect[i] {
t.Errorf("Expected\n%q\nGot\n%q\n", expect[i], got)
}

@ -55,6 +55,12 @@ or
$ helm install --set name=prod ./redis
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
contained a key called 'Test', the value set in override.yaml would take precedence:
$ helm install -f myvalues.yaml -f override.yaml ./redis
To check the generated manifests of a release without installing the chart,
the '--debug' and '--dry-run' flags can be combined. This will still require a
round-trip to the Tiller server.
@ -86,7 +92,7 @@ charts in a repository, use 'helm search'.
type installCmd struct {
name string
namespace string
valuesFile string
valueFiles valueFiles
chartPath string
dryRun bool
disableHooks bool
@ -100,6 +106,23 @@ type installCmd struct {
version string
}
type valueFiles []string
func (v *valueFiles) String() string {
return fmt.Sprint(*v)
}
func (v *valueFiles) Type() string {
return "valueFiles"
}
func (v *valueFiles) Set(value string) error {
for _, filePath := range strings.Split(value, ",") {
*v = append(*v, filePath)
}
return nil
}
func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
inst := &installCmd{
out: out,
@ -126,7 +149,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVarP(&inst.valuesFile, "values", "f", "", "specify values in a YAML file")
f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you")
f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
@ -197,19 +220,54 @@ func (i *installCmd) run() error {
return nil
}
// Merges source and destination map, preferring values from the source map
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = nextMap
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = mergeValues(destMap, nextMap)
}
return dest
}
func (i *installCmd) vals() ([]byte, error) {
base := map[string]interface{}{}
// User specified a values file via -f/--values
if i.valuesFile != "" {
bytes, err := ioutil.ReadFile(i.valuesFile)
// User specified a values files via -f/--values
for _, filePath := range i.valueFiles {
currentMap := map[string]interface{}{}
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return []byte{}, err
}
if err := yaml.Unmarshal(bytes, &base); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", i.valuesFile, err)
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
}
if err := strvals.ParseInto(i.values, base); err != nil {

@ -18,6 +18,7 @@ package main
import (
"io"
"reflect"
"regexp"
"strings"
"testing"
@ -51,6 +52,22 @@ func TestInstall(t *testing.T) {
resp: releaseMock(&releaseOptions{name: "virgil"}),
expected: "virgil",
},
// Install, values from yaml
{
name: "install with values",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("-f testdata/testcharts/alpine/extra_values.yaml", " "),
resp: releaseMock(&releaseOptions{name: "virgil"}),
expected: "virgil",
},
// Install, values from multiple yaml
{
name: "install with values",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("-f testdata/testcharts/alpine/extra_values.yaml -f testdata/testcharts/alpine/more_values.yaml", " "),
resp: releaseMock(&releaseOptions{name: "virgil"}),
expected: "virgil",
},
// Install, no charts
{
name: "install with no chart specified",
@ -172,3 +189,58 @@ func TestNameTemplate(t *testing.T) {
}
}
}
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
"testing": "fun",
}
testMap := mergeValues(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = mergeValues(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = mergeValues(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = mergeValues(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
equal = reflect.DeepEqual(testMap, expectedMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}

@ -23,7 +23,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/unversioned"
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/helm/pkg/version"
@ -37,27 +37,27 @@ const defaultImage = "gcr.io/kubernetes-helm/tiller"
// command failed.
//
// If verbose is true, this will print the manifest to stdout.
func Install(client unversioned.DeploymentsNamespacer, namespace, image string, canary, verbose bool) error {
obj := deployment(image, canary)
_, err := client.Deployments(namespace).Create(obj)
func Install(client extensionsclient.DeploymentsGetter, namespace, image string, canary, verbose bool) error {
obj := deployment(namespace, image, canary)
_, err := client.Deployments(obj.Namespace).Create(obj)
return err
}
// deployment gets the deployment object that installs Tiller.
func deployment(image string, canary bool) *extensions.Deployment {
func deployment(namespace, image string, canary bool) *extensions.Deployment {
switch {
case canary:
image = defaultImage + ":canary"
case image == "":
image = fmt.Sprintf("%s:%s", defaultImage, version.Version)
}
return generateDeployment(image)
return generateDeployment(namespace, image)
}
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource.
func DeploymentManifest(image string, canary bool) (string, error) {
obj := deployment(image, canary)
func DeploymentManifest(namespace, image string, canary bool) (string, error) {
obj := deployment(namespace, image, canary)
buf, err := yaml.Marshal(obj)
return string(buf), err
@ -68,12 +68,13 @@ func generateLabels(labels map[string]string) map[string]string {
return labels
}
func generateDeployment(image string) *extensions.Deployment {
func generateDeployment(namespace, image string) *extensions.Deployment {
labels := generateLabels(map[string]string{"name": "tiller"})
d := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Name: "tiller-deploy",
Labels: labels,
Namespace: namespace,
Name: "tiller-deploy",
Labels: labels,
},
Spec: extensions.DeploymentSpec{
Replicas: 1,
@ -86,7 +87,7 @@ func generateDeployment(image string) *extensions.Deployment {
{
Name: "tiller",
Image: image,
ImagePullPolicy: "Always",
ImagePullPolicy: "IfNotPresent",
Ports: []api.ContainerPort{{ContainerPort: 44134, Name: "tiller"}},
LivenessProbe: &api.Probe{
Handler: api.Handler{

@ -21,12 +21,13 @@ import (
"testing"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/version"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
testcore "k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/helm/pkg/version"
)
func TestDeploymentManifest(t *testing.T) {
@ -44,7 +45,7 @@ func TestDeploymentManifest(t *testing.T) {
for _, tt := range tests {
o, err := DeploymentManifest(tt.image, tt.canary)
o, err := DeploymentManifest(api.NamespaceDefault, tt.image, tt.canary)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
@ -62,9 +63,9 @@ func TestDeploymentManifest(t *testing.T) {
func TestInstall(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
fake := testclient.Fake{}
fake.AddReactor("create", "deployments", func(action testclient.Action) (bool, runtime.Object, error) {
obj := action.(testclient.CreateAction).GetObject().(*extensions.Deployment)
fake := fake.NewSimpleClientset()
fake.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*extensions.Deployment)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
@ -76,16 +77,16 @@ func TestInstall(t *testing.T) {
return true, obj, nil
})
err := Install(fake.Extensions(), "default", image, false, false)
err := Install(fake.Extensions(), api.NamespaceDefault, image, false, false)
if err != nil {
t.Errorf("unexpected error: %#+v", err)
}
}
func TestInstall_canary(t *testing.T) {
fake := testclient.Fake{}
fake.AddReactor("create", "deployments", func(action testclient.Action) (bool, runtime.Object, error) {
obj := action.(testclient.CreateAction).GetObject().(*extensions.Deployment)
fake := fake.NewSimpleClientset()
fake.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*extensions.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != "gcr.io/kubernetes-helm/tiller:canary" {
t.Errorf("expected canary image, got '%s'", i)
@ -93,7 +94,7 @@ func TestInstall_canary(t *testing.T) {
return true, obj, nil
})
err := Install(fake.Extensions(), "default", "", true, false)
err := Install(fake.Extensions(), api.NamespaceDefault, "", true, false)
if err != nil {
t.Errorf("unexpected error: %#+v", err)
}

@ -67,6 +67,7 @@ type listCmd struct {
out io.Writer
all bool
deleted bool
deleting bool
deployed bool
failed bool
superseded bool
@ -104,6 +105,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.StringVarP(&list.offset, "offset", "o", "", "next release name in the list, used to offset from start value")
f.BoolVar(&list.all, "all", false, "show all releases, not just the ones marked DEPLOYED")
f.BoolVar(&list.deleted, "deleted", false, "show deleted releases")
f.BoolVar(&list.deleting, "deleting", false, "show releases that are currently being deleted")
f.BoolVar(&list.deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
f.BoolVar(&list.failed, "failed", false, "show failed releases")
// TODO: Do we want this as a feature of 'helm list'?
@ -165,9 +167,7 @@ func (l *listCmd) statusCodes() []release.Status_Code {
release.Status_UNKNOWN,
release.Status_DEPLOYED,
release.Status_DELETED,
// TODO: Should we return superseded records? These are records
// that were replaced by an upgrade.
//release.Status_SUPERSEDED,
release.Status_DELETING,
release.Status_FAILED,
}
}
@ -178,6 +178,9 @@ func (l *listCmd) statusCodes() []release.Status_Code {
if l.deleted {
status = append(status, release.Status_DELETED)
}
if l.deleting {
status = append(status, release.Status_DELETING)
}
if l.failed {
status = append(status, release.Status_FAILED)
}
@ -194,7 +197,7 @@ func (l *listCmd) statusCodes() []release.Status_Code {
func formatList(rels []*release.Release) string {
table := uitable.New()
table.MaxColWidth = 30
table.MaxColWidth = 60
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART")
for _, r := range rels {
c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version)

@ -77,6 +77,16 @@ func TestListCmd(t *testing.T) {
// See note on previous test.
expected: "thomas-guide\natlas-guide",
},
{
name: "with a release, multiple flags, deleting",
args: []string{"--all", "-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETING}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
},
// See note on previous test.
expected: "thomas-guide\natlas-guide",
},
}
var buf bytes.Buffer

@ -19,6 +19,7 @@ import (
"bytes"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@ -121,11 +122,16 @@ func TestLoadPlugins(t *testing.T) {
if pp.Long != tt.long {
t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long)
}
if err := pp.RunE(pp, tt.args); err != nil {
t.Errorf("Error running %s: %s", tt.use, err)
}
if out.String() != tt.expect {
t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
// Currently, plugins assume a Linux subsystem. Skip the execution
// tests until this is fixed
if runtime.GOOS != "windows" {
if err := pp.RunE(pp, tt.args); err != nil {
t.Errorf("Error running %s: %s", tt.use, err)
}
if out.String() != tt.expect {
t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
}
}
}
}

@ -24,7 +24,7 @@ package search
import (
"errors"
"path/filepath"
"path"
"regexp"
"sort"
"strings"
@ -70,7 +70,9 @@ func (i *Index) AddRepo(rname string, ind *repo.IndexFile, all bool) {
// By convention, an index file is supposed to have the newest at the
// 0 slot, so our best bet is to grab the 0 entry and build the index
// entry off of that.
fname := filepath.Join(rname, name)
// Note: Do not use filePath.Join since on Windows it will return \
// which results in a repo name that cannot be understood.
fname := path.Join(rname, name)
if !all {
i.lines[fname] = indstr(rname, ref[0])
i.charts[fname] = ref[0]

@ -0,0 +1,2 @@
test:
Name: extra-values

@ -0,0 +1,2 @@
test:
Name: more-values

@ -12,6 +12,7 @@ metadata:
release: {{.Release.Name | quote }}
# This makes it easy to audit chart usage.
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
values: {{.Values.test.Name}}
annotations:
"helm.sh/created": {{.Release.Time.Seconds | quote }}
spec:

@ -20,7 +20,7 @@ import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/helm/pkg/kube"
@ -35,16 +35,16 @@ func newTillerPortForwarder(namespace, context string) (*kube.Tunnel, error) {
return nil, err
}
podName, err := getTillerPodName(client, namespace)
podName, err := getTillerPodName(client.Core(), namespace)
if err != nil {
return nil, err
}
const tillerPort = 44134
t := kube.NewTunnel(client.RESTClient, config, namespace, podName, tillerPort)
t := kube.NewTunnel(client.Core().RESTClient(), config, namespace, podName, tillerPort)
return t, t.ForwardPort()
}
func getTillerPodName(client unversioned.PodsNamespacer, namespace string) (string, error) {
func getTillerPodName(client internalversion.PodsGetter, namespace string) (string, error) {
// TODO use a const for labels
selector := labels.Set{"app": "helm", "name": "tiller"}.AsSelector()
pod, err := getFirstRunningPod(client, namespace, selector)
@ -54,7 +54,7 @@ func getTillerPodName(client unversioned.PodsNamespacer, namespace string) (stri
return pod.ObjectMeta.GetName(), nil
}
func getFirstRunningPod(client unversioned.PodsNamespacer, namespace string, selector labels.Selector) (*api.Pod, error) {
func getFirstRunningPod(client internalversion.PodsGetter, namespace string, selector labels.Selector) (*api.Pod, error) {
options := api.ListOptions{LabelSelector: selector}
pods, err := client.Pods(namespace).List(options)
if err != nil {

@ -20,7 +20,7 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
func mockTillerPod() api.Pod {
@ -74,8 +74,8 @@ func TestGetFirstPod(t *testing.T) {
}
for _, tt := range tests {
client := testclient.NewSimpleFake(&api.PodList{Items: tt.pods})
name, err := getTillerPodName(client, api.NamespaceDefault)
client := fake.NewSimpleClientset(&api.PodList{Items: tt.pods})
name, err := getTillerPodName(client.Core(), api.NamespaceDefault)
if (err != nil) != tt.err {
t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err)
}

@ -40,6 +40,12 @@ version will be specified unless the '--version' flag is set.
To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line.
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
contained a key called 'Test', the value set in override.yaml would take precedence:
$ helm install -f myvalues.yaml -f override.yaml ./redis
`
type upgradeCmd struct {
@ -49,7 +55,7 @@ type upgradeCmd struct {
client helm.Interface
dryRun bool
disableHooks bool
valuesFile string
valueFiles valueFiles
values string
verify bool
keyring string
@ -84,7 +90,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file")
f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.StringVar(&upgrade.values, "set", "", "set values on the command line. Separate values with commas: key1=val1,key2=val2")
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
@ -121,7 +127,7 @@ func (u *upgradeCmd) run() error {
client: u.client,
out: u.out,
name: u.release,
valuesFile: u.valuesFile,
valueFiles: u.valueFiles,
dryRun: u.dryRun,
verify: u.verify,
disableHooks: u.disableHooks,
@ -164,16 +170,19 @@ func (u *upgradeCmd) run() error {
func (u *upgradeCmd) vals() ([]byte, error) {
base := map[string]interface{}{}
// User specified a values file via -f/--values
if u.valuesFile != "" {
bytes, err := ioutil.ReadFile(u.valuesFile)
// User specified a values files via -f/--values
for _, filePath := range u.valueFiles {
currentMap := map[string]interface{}{}
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return []byte{}, err
}
if err := yaml.Unmarshal(bytes, base); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", u.valuesFile, err)
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
}
if err := strvals.ParseInto(u.values, base); err != nil {

@ -17,10 +17,22 @@ package main
import (
"bytes"
"fmt"
"runtime"
"testing"
)
func TestVerifyCmd(t *testing.T) {
statExe := "stat"
statPathMsg := "no such file or directory"
statFileMsg := statPathMsg
if runtime.GOOS == "windows" {
statExe = "GetFileAttributesEx"
statPathMsg = "The system cannot find the path specified."
statFileMsg = "The system cannot find the file specified."
}
tests := []struct {
name string
args []string
@ -36,7 +48,7 @@ func TestVerifyCmd(t *testing.T) {
{
name: "verify requires that chart exists",
args: []string{"no/such/file"},
expect: "stat no/such/file: no such file or directory",
expect: fmt.Sprintf("%s no/such/file: %s", statExe, statPathMsg),
err: true,
},
{
@ -48,7 +60,7 @@ func TestVerifyCmd(t *testing.T) {
{
name: "verify requires that chart has prov file",
args: []string{"testdata/testcharts/compressedchart-0.1.0.tgz"},
expect: "could not load provenance file testdata/testcharts/compressedchart-0.1.0.tgz.prov: stat testdata/testcharts/compressedchart-0.1.0.tgz.prov: no such file or directory",
expect: fmt.Sprintf("could not load provenance file testdata/testcharts/compressedchart-0.1.0.tgz.prov: %s testdata/testcharts/compressedchart-0.1.0.tgz.prov: %s", statExe, statFileMsg),
err: true,
},
{

@ -24,8 +24,8 @@ import (
"os"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
@ -41,7 +41,7 @@ const (
// rootServer is the root gRPC server.
//
// Each gRPC service registers itself to this server during init().
var rootServer = grpc.NewServer()
var rootServer = tiller.NewServer()
// env is the default environment.
//
@ -81,15 +81,16 @@ func main() {
}
func start(c *cobra.Command, args []string) {
clientset, err := kube.New(nil).ClientSet()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot initialize Kubernetes connection: %s", err)
}
switch store {
case storageMemory:
env.Releases = storage.Init(driver.NewMemory())
case storageConfigMap:
c, err := env.KubeClient.APIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot initialize Kubernetes connection: %s", err)
}
env.Releases = storage.Init(driver.NewConfigMaps(c.ConfigMaps(environment.TillerNamespace)))
env.Releases = storage.Init(driver.NewConfigMaps(clientset.Core().ConfigMaps(environment.TillerNamespace)))
}
lstn, err := net.Listen("tcp", grpcAddr)
@ -109,7 +110,7 @@ func start(c *cobra.Command, args []string) {
srvErrCh := make(chan error)
probeErrCh := make(chan error)
go func() {
svc := tiller.NewReleaseServer(env)
svc := tiller.NewReleaseServer(env, clientset)
services.RegisterReleaseServiceServer(rootServer, svc)
if err := rootServer.Serve(lstn); err != nil {
srvErrCh <- err

@ -9,6 +9,19 @@ Helm provides access to files through the `.Files` object. Before we get going w
- Files in `templates/` cannot be accessed.
- Charts to not preserve UNIX mode information, so file-level permissions will have no impact on the availability of a file when it comes to the `.Files` object.
<!-- (see https://github.com/jonschlinkert/markdown-toc) -->
<!-- toc -->
- [Basic example](#basic-example)
- [Path helpers](#path-helpers)
- [Glob patterns](#glob-patterns)
- [ConfigMap and Secrets utility functions](#configmap-and-secrets-utility-functions)
- [Secrets](#secrets)
- [Lines](#lines)
<!-- tocstop -->
## Basic example
With those caveats behind, let's write a template that reads three files into our ConfigMap. To get started, we will add three files to the chart, putting all three directly inside of the `mychart/` directory.
@ -67,11 +80,29 @@ data:
message = Goodbye from config 3
```
## Path helpers
When working with files, it can be very useful to perform some standard
operations on the file paths themselves. To help with this, Helm imports many of
the functions from Go's [path](https://golang.org/pkg/path/) package for your
use. They are all accessible with the same names as in the Go package, but
with a lowercase first letter. For example, `Base` becomes `base`, etc.
The imported functions are:
- Base
- Dir
- Ext
- IsAbs
- Clean
## Glob patterns
As your chart grows, you may find you have a greater need to organize your
files more, and so we provide a `Files.Glob(pattern string)` method to assist
in extracting certain files with all the flexibility of [glob patterns](//godoc.org/github.com/gobwas/glob).
in extracting certain files with all the flexibility of [glob patterns](https://godoc.org/github.com/gobwas/glob).
`.Glob` returns a `Files` type, so you may call any of the `Files` methods on
the returned object.
For example, imagine the directory structure:
@ -101,6 +132,36 @@ Or
{{ end }}
```
## ConfigMap and Secrets utility functions
(Not present in version 2.0.2 or prior)
It is very common to want to place file content into both configmaps and
secrets, for mounting into your pods at run time. To help with this, we provide a
couple utility methods on the `Files` type.
For further organization, it is especially useful to use these methods in
conjunction with the `Glob` method.
Given the directory structure from the [Glob][Glob patterns] example above:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: conf
data:
{{ (.Files.Glob "foo/*").AsConfig | indent 2 }}
---
apiVersion: v1
kind: Secret
metadata:
name: very-secret
type: Opaque
data:
{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}
```
## Secrets
When working with a Secret resource, you can import a file and have the template base-64 encode it for you:
@ -130,6 +191,17 @@ data:
bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK
```
## Lines
Sometimes it is desireable to access each line of a file in your template. We
provide a convenient `Lines` method for this.
```yaml
data:
some-file.txt: {{ range .Files.Lines "foo/bar.txt" }}
{{ . }}{{ end }}
```
Currently, there is no way to pass files external to the chart during `helm install`. So if you are asking users to supply data, it must be loaded using `helm install -f` or `helm install --set`.
This discussion wraps up our dive into the tools and techniques for writing Helm templates. In the next section we will see how you can use one special file, `templates/NOTES.txt`, to send post-installation instructions to the users of your chart.

@ -2,7 +2,7 @@
{{/*
Expand the name of the chart.
*/}}
{{define "name"}}{{default "nginx" .Values.nameOverride | trunc 24 }}{{end}}
{{define "name"}}{{default "nginx" .Values.nameOverride | trunc 24 | trimSuffix "-" }}{{end}}
{{/*
Create a default fully qualified app name.
@ -12,5 +12,5 @@ We truncate at 24 chars because some Kubernetes name fields are limited to this
*/}}
{{define "fullname"}}
{{- $name := default "nginx" .Values.nameOverride -}}
{{printf "%s-%s" .Release.Name $name | trunc 24 -}}
{{printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{end}}

@ -106,6 +106,34 @@ follows:
See this issue for more information: https://github.com/kubernetes/helm/issues/1455
**Q: On GKE (Google Container Engine) I get "No SSH tunnels currently open"**
```
Error: Error forwarding ports: error upgrading connection: No SSH tunnels currently open. Were the targets able to accept an ssh-key for user "gke-[redacted]"?
```
Another variation of the error message is:
```
Unable to connect to the server: x509: certificate signed by unknown authority
```
A: The issue is that your local Kubernetes config file must have the correct credentials.
When you create a cluster on GKE, it will give you credentials, including SSL
certificates and certificate authorities. These need to be stored in a Kubernetes
config file (Default: `~/.kube/config` so that `kubectl` and `helm` can access
them.
**Q: When I run a Helm command, I get an error about the tunnel or proxy**
A: Helm uses the Kubernetes proxy service to connect to the Tiller server.
If the command `kubectl proxy` does not work for you, neither will Helm.
Typically, the error is related to a missing `socat` service.
## Upgrading
My Helm used to work, then I upgrade. Now it is broken.

265
glide.lock generated

@ -1,5 +1,5 @@
hash: 8ae84a3225f6cc31f91cc42dc8cf816792a989838254964cdcaaa57c75c37cdc
updated: 2016-12-01T17:35:05.940550036-07:00
hash: 2fc61aa64319b4dc6cd4a107c46a25264865f2e07362d7dcd8a3db002278d6a6
updated: 2016-12-13T17:07:47.402783003-05:00
imports:
- name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
@ -17,22 +17,13 @@ imports:
- name: github.com/blang/semver
version: 31b736133b98f26d5e078ec9eb591666edfd091f
- name: github.com/coreos/go-oidc
version: 5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b
version: 5644a2f50e2d2d5ba0b474bc5bc55fea1925936d
subpackages:
- http
- jose
- key
- oauth2
- oidc
- name: github.com/coreos/go-systemd
version: 4484981625c1a6a2ecb40a390fcb6a9bcfee76e3
subpackages:
- activation
- daemon
- dbus
- journal
- unit
- util
- name: github.com/coreos/pkg
version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8
subpackages:
@ -49,6 +40,8 @@ imports:
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/dgrijalva/jwt-go
version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20
- name: github.com/docker/distribution
version: cd27f179f2c10c5d300e6d09025b538c475b0d51
subpackages:
@ -101,10 +94,20 @@ imports:
- swagger
- name: github.com/evanphx/json-patch
version: 465937c80b3c07a7c7ad20cc934898646a91c1de
- name: github.com/exponent-io/jsonpath
version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5
- name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-openapi/jsonpointer
version: 46af16f9f7b149af66e5d1bd010e3574dc06de98
- name: github.com/go-openapi/jsonreference
version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
- name: github.com/go-openapi/spec
version: 6aced65f8501fe1217321abf0749d354824ba2ff
- name: github.com/go-openapi/swag
version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72
- name: github.com/gobwas/glob
version: 0354991b92587e2742549d3036f3b5bae5ab03f2
version: bea32b9cd2d6f55753d94a28e959b13f0244797a
subpackages:
- compiler
- match
@ -153,50 +156,6 @@ imports:
- proto
- ptypes/any
- ptypes/timestamp
- name: github.com/google/cadvisor
version: a726d13de8cb32860e73d72a78dc8e0124267709
subpackages:
- api
- cache/memory
- client/v2
- collector
- container
- container/common
- container/docker
- container/libcontainer
- container/raw
- container/rkt
- container/systemd
- devicemapper
- events
- fs
- healthz
- http
- http/mux
- info/v1
- info/v1/test
- info/v2
- machine
- manager
- manager/watcher
- manager/watcher/raw
- manager/watcher/rkt
- metrics
- pages
- pages/static
- storage
- summary
- utils
- utils/cloudinfo
- utils/cpuload
- utils/cpuload/netlink
- utils/docker
- utils/oomparser
- utils/sysfs
- utils/sysinfo
- utils/tail
- validate
- version
- name: github.com/google/gofuzz
version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5
- name: github.com/gosuri/uitable
@ -204,6 +163,8 @@ imports:
subpackages:
- util/strutil
- util/wordwrap
- name: github.com/howeyc/gopass
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
- name: github.com/imdario/mergo
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- name: github.com/inconshreveable/mousetrap
@ -212,14 +173,26 @@ imports:
version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982
- name: github.com/juju/ratelimit
version: 77ed1c8a01217656d2080ad51981f6e99adaa177
- name: github.com/mailru/easyjson
version: d5b7844b561a7bc640052f1b935f7b800330d7e0
subpackages:
- buffer
- jlexer
- jwriter
- name: github.com/Masterminds/semver
version: 52edfc04e184ecf0962489d167b511b27aeebd61
version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/Masterminds/sprig
version: 1e60e4ce482a1e2c7b9c9be667535ef152e04300
- name: github.com/mattn/go-runewidth
version: d6bea18f789704b5f83375793155289da36a3c7f
- name: github.com/mitchellh/go-wordwrap
version: ad45545899c7b13c020ea92b2072220eefad42b8
- name: github.com/pborman/uuid
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
- name: github.com/PuerkitoBio/purell
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/russross/blackfriday
version: 300106c228d52c8941d4b3de6054a6062a86dda3
- name: github.com/satori/go.uuid
@ -229,11 +202,11 @@ imports:
- name: github.com/Sirupsen/logrus
version: 51fe59aca108dc5680109e7b2051cbdcfa5a253c
- name: github.com/spf13/cobra
version: 6a8bd97bdb1fc0d08a83459940498ea49d3e8c93
version: f62e98d28ab7ad31d707ba837a966378465c7b57
subpackages:
- doc
- name: github.com/spf13/pflag
version: 367864438f1b1a3c7db4da06a2f55b144e6784e0
version: 5ccb023bc27df288a957c5e994cd44fd19619465
- name: github.com/technosophos/moniker
version: 9f956786b91d9786ca11aa5be6104542fa911546
- name: github.com/ugorji/go
@ -254,13 +227,15 @@ imports:
- openpgp/s2k
- ssh/terminal
- name: golang.org/x/net
version: fb93926129b8ec0056f2f458b1f519654814edf0
version: e90d6d0afc4c315a0d87a568ae68577cc15149a0
subpackages:
- context
- context/ctxhttp
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- trace
- websocket
- name: golang.org/x/oauth2
@ -270,6 +245,23 @@ imports:
- internal
- jws
- jwt
- name: golang.org/x/sys
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
subpackages:
- unix
- name: golang.org/x/text
version: 2910a502d2bf9e43193af9d68ca516529614eed3
subpackages:
- cases
- internal/tag
- language
- runes
- secure/bidirule
- secure/precis
- transform
- unicode/bidi
- unicode/norm
- width
- name: google.golang.org/appengine
version: 4f7eeb5305a4ba1966344836ba4af9996b7b4e05
subpackages:
@ -297,88 +289,24 @@ imports:
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
- name: k8s.io/client-go
version: 0b62e254fe853d89b1d8d3445bbdab11bcc11bc3
subpackages:
- 1.4/pkg/api
- 1.4/pkg/api/endpoints
- 1.4/pkg/api/errors
- 1.4/pkg/api/meta
- 1.4/pkg/api/meta/metatypes
- 1.4/pkg/api/pod
- 1.4/pkg/api/resource
- 1.4/pkg/api/service
- 1.4/pkg/api/unversioned
- 1.4/pkg/api/unversioned/validation
- 1.4/pkg/api/util
- 1.4/pkg/api/v1
- 1.4/pkg/api/validation
- 1.4/pkg/apimachinery
- 1.4/pkg/apimachinery/registered
- 1.4/pkg/apis/autoscaling
- 1.4/pkg/apis/batch
- 1.4/pkg/apis/extensions
- 1.4/pkg/auth/user
- 1.4/pkg/capabilities
- 1.4/pkg/conversion
- 1.4/pkg/conversion/queryparams
- 1.4/pkg/fields
- 1.4/pkg/labels
- 1.4/pkg/runtime
- 1.4/pkg/runtime/serializer
- 1.4/pkg/runtime/serializer/json
- 1.4/pkg/runtime/serializer/protobuf
- 1.4/pkg/runtime/serializer/recognizer
- 1.4/pkg/runtime/serializer/streaming
- 1.4/pkg/runtime/serializer/versioning
- 1.4/pkg/security/apparmor
- 1.4/pkg/selection
- 1.4/pkg/third_party/forked/golang/reflect
- 1.4/pkg/types
- 1.4/pkg/util
- 1.4/pkg/util/clock
- 1.4/pkg/util/config
- 1.4/pkg/util/crypto
- 1.4/pkg/util/errors
- 1.4/pkg/util/flowcontrol
- 1.4/pkg/util/framer
- 1.4/pkg/util/hash
- 1.4/pkg/util/integer
- 1.4/pkg/util/intstr
- 1.4/pkg/util/json
- 1.4/pkg/util/labels
- 1.4/pkg/util/net
- 1.4/pkg/util/net/sets
- 1.4/pkg/util/parsers
- 1.4/pkg/util/rand
- 1.4/pkg/util/runtime
- 1.4/pkg/util/sets
- 1.4/pkg/util/uuid
- 1.4/pkg/util/validation
- 1.4/pkg/util/validation/field
- 1.4/pkg/util/wait
- 1.4/pkg/util/yaml
- 1.4/pkg/version
- 1.4/pkg/watch
- 1.4/pkg/watch/versioned
- 1.4/rest
- 1.4/tools/clientcmd/api
- 1.4/tools/metrics
- 1.4/transport
- name: k8s.io/kubernetes
version: fd8fac83034df346529c6e11aabceea2db48d663
version: d47846323632bf59c729460fc7344d2df347bf46
subpackages:
- cmd/kubeadm/app/apis/kubeadm
- cmd/kubeadm/app/apis/kubeadm/install
- cmd/kubeadm/app/apis/kubeadm/v1alpha1
- federation/apis/federation
- federation/apis/federation/install
- federation/apis/federation/v1beta1
- federation/client/clientset_generated/federation_internalclientset
- federation/client/clientset_generated/federation_internalclientset/typed/core/unversioned
- federation/client/clientset_generated/federation_internalclientset/typed/extensions/unversioned
- federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned
- federation/client/clientset_generated/federation_internalclientset/typed/core/internalversion
- federation/client/clientset_generated/federation_internalclientset/typed/extensions/internalversion
- federation/client/clientset_generated/federation_internalclientset/typed/federation/internalversion
- pkg/api
- pkg/api/annotations
- pkg/api/endpoints
- pkg/api/errors
- pkg/api/events
- pkg/api/install
- pkg/api/meta
- pkg/api/meta/metatypes
@ -392,11 +320,13 @@ imports:
- pkg/api/util
- pkg/api/v1
- pkg/api/validation
- pkg/api/validation/path
- pkg/apimachinery
- pkg/apimachinery/announced
- pkg/apimachinery/registered
- pkg/apis/apps
- pkg/apis/apps/install
- pkg/apis/apps/v1alpha1
- pkg/apis/apps/v1beta1
- pkg/apis/authentication
- pkg/apis/authentication/install
- pkg/apis/authentication/v1beta1
@ -425,43 +355,60 @@ imports:
- pkg/apis/imagepolicy/v1alpha1
- pkg/apis/policy
- pkg/apis/policy/install
- pkg/apis/policy/v1alpha1
- pkg/apis/policy/v1beta1
- pkg/apis/rbac
- pkg/apis/rbac/install
- pkg/apis/rbac/v1alpha1
- pkg/apis/storage
- pkg/apis/storage/install
- pkg/apis/storage/util
- pkg/apis/storage/v1beta1
- pkg/auth/authenticator
- pkg/auth/user
- pkg/capabilities
- pkg/client/cache
- pkg/client/clientset_generated/internalclientset
- pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned
- pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned
- pkg/client/clientset_generated/internalclientset/typed/autoscaling/unversioned
- pkg/client/clientset_generated/internalclientset/typed/batch/unversioned
- pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned
- pkg/client/clientset_generated/internalclientset/typed/core/unversioned
- pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned
- pkg/client/clientset_generated/internalclientset/typed/rbac/unversioned
- pkg/client/clientset_generated/internalclientset/typed/storage/unversioned
- pkg/client/clientset_generated/internalclientset/fake
- pkg/client/clientset_generated/internalclientset/typed/apps/internalversion
- pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion
- pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion
- pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion
- pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/batch/internalversion
- pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion
- pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/core/internalversion
- pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion
- pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/policy/internalversion
- pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion
- pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/storage/internalversion
- pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake
- pkg/client/metrics
- pkg/client/record
- pkg/client/restclient
- pkg/client/restclient/fake
- pkg/client/retry
- pkg/client/testing/core
- pkg/client/transport
- pkg/client/typed/discovery
- pkg/client/typed/discovery/fake
- pkg/client/typed/dynamic
- pkg/client/unversioned
- pkg/client/unversioned/adapters/internalclientset
- pkg/client/unversioned/auth
- pkg/client/unversioned/clientcmd
- pkg/client/unversioned/clientcmd/api
- pkg/client/unversioned/clientcmd/api/latest
- pkg/client/unversioned/clientcmd/api/v1
- pkg/client/unversioned/fake
- pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand
- pkg/client/unversioned/testclient
- pkg/controller
- pkg/controller/deployment/util
- pkg/conversion
@ -469,8 +416,10 @@ imports:
- pkg/credentialprovider
- pkg/fieldpath
- pkg/fields
- pkg/genericapiserver/openapi/common
- pkg/httplog
- pkg/kubectl
- pkg/kubectl/cmd/testing
- pkg/kubectl/cmd/util
- pkg/kubectl/resource
- pkg/kubelet/qos
@ -479,7 +428,7 @@ imports:
- pkg/kubelet/types
- pkg/labels
- pkg/master/ports
- pkg/registry/thirdpartyresourcedata
- pkg/registry/extensions/thirdpartyresourcedata
- pkg/runtime
- pkg/runtime/serializer
- pkg/runtime/serializer/json
@ -488,15 +437,17 @@ imports:
- pkg/runtime/serializer/streaming
- pkg/runtime/serializer/versioning
- pkg/security/apparmor
- pkg/security/podsecuritypolicy/seccomp
- pkg/security/podsecuritypolicy/util
- pkg/selection
- pkg/serviceaccount
- pkg/storage
- pkg/types
- pkg/util
- pkg/util/certificates
- pkg/util/cert
- pkg/util/clock
- pkg/util/config
- pkg/util/crypto
- pkg/util/diff
- pkg/util/errors
- pkg/util/exec
- pkg/util/flag
@ -514,10 +465,10 @@ imports:
- pkg/util/labels
- pkg/util/net
- pkg/util/net/sets
- pkg/util/node
- pkg/util/parsers
- pkg/util/pod
- pkg/util/rand
- pkg/util/replicaset
- pkg/util/runtime
- pkg/util/sets
- pkg/util/slice
@ -539,4 +490,14 @@ imports:
- third_party/forked/golang/netutil
- third_party/forked/golang/reflect
- third_party/forked/golang/template
testImports: []
testImports:
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert
- mock
- require

@ -1,15 +1,18 @@
package: k8s.io/helm
import:
- package: golang.org/x/net
version: fb93926129b8ec0056f2f458b1f519654814edf0
version: e90d6d0afc4c315a0d87a568ae68577cc15149a0
subpackages:
- context
- package: github.com/spf13/cobra
version: f62e98d28ab7ad31d707ba837a966378465c7b57
- package: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465
- package: github.com/Masterminds/sprig
version: ^2.7
- package: github.com/ghodss/yaml
- package: github.com/Masterminds/semver
version: ~1.2.1
version: ~1.2.2
- package: github.com/technosophos/moniker
- package: github.com/golang/protobuf
version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e
@ -20,7 +23,7 @@ import:
- package: google.golang.org/grpc
version: 1.0.3
- package: k8s.io/kubernetes
version: ~1.4.1
version: ~1.5.0
subpackages:
- pkg/api
- pkg/api/errors
@ -28,12 +31,14 @@ import:
- pkg/apimachinery/registered
- pkg/apis/batch
- pkg/apis/extensions
- pkg/client/clientset_generated/internalclientset
- pkg/client/clientset_generated/internalclientset/typed/core/internalversion
- pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion
- pkg/client/restclient
- pkg/client/unversioned
- pkg/client/typed/discovery
- pkg/client/unversioned/clientcmd
- pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand
- pkg/client/unversioned/testclient
- pkg/kubectl
- pkg/kubectl/cmd/util
- pkg/kubectl/resource
@ -41,7 +46,6 @@ import:
- pkg/runtime
- pkg/util/intstr
- pkg/util/strategicpatch
- pkg/util/yaml
- pkg/watch
- package: github.com/gosuri/uitable
- package: github.com/asaskevich/govalidator
@ -51,3 +55,8 @@ import:
- openpgp
- package: github.com/gobwas/glob
version: ^0.2.1
testImports:
- package: github.com/stretchr/testify
version: ^1.1.4
subpackages:
- assert

@ -162,7 +162,7 @@ const defaultHelpers = `{{/* vim: set filetype=mustache: */}}
Expand the name of the chart.
*/}}
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 24 -}}
{{- default .Chart.Name .Values.nameOverride | trunc 24 | trimSuffix "-" -}}
{{- end -}}
{{/*
@ -171,7 +171,7 @@ We truncate at 24 chars because some Kubernetes name fields are limited to this
*/}}
{{- define "fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 24 -}}
{{- printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{- end -}}
`

@ -16,6 +16,12 @@ limitations under the License.
package chartutil
import (
"encoding/base64"
"path"
"strings"
yaml "gopkg.in/yaml.v2"
"github.com/gobwas/glob"
"github.com/golang/protobuf/ptypes/any"
)
@ -83,3 +89,88 @@ func (f Files) Glob(pattern string) Files {
return nf
}
// AsConfig turns a Files group and flattens it to a YAML map suitable for
// including in the `data` section of a kubernetes ConfigMap definition.
// Duplicate keys will be overwritten, so be aware that your filenames
// (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
// object is nil.
//
// The output will not be indented, so you will want to pipe this to the
// `indent` template function.
//
// data:
// {{ .Files.Glob("config/**").AsConfig() | indent 4 }}
func (f Files) AsConfig() string {
if f == nil {
return ""
}
m := map[string]string{}
// Explicitly convert to strings, and file names
for k, v := range f {
m[path.Base(k)] = string(v)
}
return ToYaml(m)
}
// AsSecrets returns the value of a Files object as base64 suitable for
// including in the `data` section of a kubernetes Secret definition.
// Duplicate keys will be overwritten, so be aware that your filenames
// (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
// object is nil.
//
// The output will not be indented, so you will want to pipe this to the
// `indent` template function.
//
// data:
// {{ .Files.Glob("secrets/*").AsSecrets() }}
func (f Files) AsSecrets() string {
if f == nil {
return ""
}
m := map[string]string{}
for k, v := range f {
m[path.Base(k)] = base64.StdEncoding.EncodeToString(v)
}
return ToYaml(m)
}
// Lines returns each line of a named file (split by "\n") as a slice, so it can
// be ranged over in your templates.
//
// This is designed to be called from a template.
//
// {{ range .Files.Lines "foo/bar.html" }}
// {{ . }}{{ end }}
func (f Files) Lines(path string) []string {
if f == nil || f[path] == nil {
return []string{}
}
return strings.Split(string(f[path]), "\n")
}
// 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 {
data, err := yaml.Marshal(v)
if err != nil {
// Swallow errors inside of a template.
return ""
}
return string(data)
}

@ -19,6 +19,7 @@ import (
"testing"
"github.com/golang/protobuf/ptypes/any"
"github.com/stretchr/testify/assert"
)
var cases = []struct {
@ -28,6 +29,7 @@ var cases = []struct {
{"ship/stowaway.txt", "Legatt"},
{"story/name.txt", "The Secret Sharer"},
{"story/author.txt", "Joseph Conrad"},
{"multiline/test.txt", "bar\nfoo"},
}
func getTestFiles() []*any.Any {
@ -55,16 +57,56 @@ func TestNewFiles(t *testing.T) {
}
func TestFileGlob(t *testing.T) {
as := assert.New(t)
f := NewFiles(getTestFiles())
matched := f.Glob("story/**")
if len(matched) != 2 {
t.Errorf("Expected two files in glob story/**, got %d", len(matched))
as.Len(matched, 2, "Should be two files in glob story/**")
as.Equal("Joseph Conrad", matched.Get("story/author.txt"))
}
func TestToConfig(t *testing.T) {
as := assert.New(t)
f := NewFiles(getTestFiles())
out := f.Glob("**/captain.txt").AsConfig()
as.Equal("captain.txt: The Captain\n", out)
out = f.Glob("ship/**").AsConfig()
as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt\n", out)
}
func TestToSecret(t *testing.T) {
as := assert.New(t)
f := NewFiles(getTestFiles())
out := f.Glob("ship/**").AsSecrets()
as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0\n", out)
}
func TestLines(t *testing.T) {
as := assert.New(t)
f := NewFiles(getTestFiles())
out := f.Lines("multiline/test.txt")
as.Len(out, 2)
as.Equal("bar", out[0])
}
func TestToYaml(t *testing.T) {
expect := "foo: bar\n"
v := struct {
Foo string `json:"foo"`
}{
Foo: "bar",
}
m, expect := matched.Get("story/author.txt"), "Joseph Conrad"
if m != expect {
t.Errorf("Wrong globbed file content. Expected %s, got %s", expect, m)
if got := ToYaml(v); got != expect {
t.Errorf("Expected %q, got %q", expect, got)
}
}

@ -229,6 +229,10 @@ func LoadDir(dir string) (*chart.Chart, error) {
err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error {
n := strings.TrimPrefix(name, topdir)
// Normalize to / since it will also work on Windows
n = filepath.ToSlash(n)
if err != nil {
return err
}

@ -24,7 +24,6 @@ import (
"text/template"
"github.com/Masterminds/sprig"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart"
@ -69,24 +68,21 @@ func FuncMap() template.FuncMap {
delete(f, "env")
delete(f, "expandenv")
// Add a function to convert to YAML:
f["toYaml"] = toYaml
// Add some extra functionality
extra := template.FuncMap{
"toYaml": chartutil.ToYaml,
// This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the
// integrity of the linter.
f["include"] = func(string, interface{}) string { return "not implemented" }
return f
}
// This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the
// integrity of the linter.
"include": func(string, interface{}) string { return "not implemented" },
}
func toYaml(v interface{}) string {
data, err := yaml.Marshal(v)
if err != nil {
// Swallow errors inside of a template.
return ""
for k, v := range extra {
f[k] = v
}
return string(data)
return f
}
// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates.

@ -27,19 +27,6 @@ import (
"github.com/golang/protobuf/ptypes/any"
)
func TestToYaml(t *testing.T) {
expect := "foo: bar\n"
v := struct {
Foo string `json:"foo"`
}{
Foo: "bar",
}
if got := toYaml(v); got != expect {
t.Errorf("Expected %q, got %q", expect, got)
}
}
func TestEngine(t *testing.T) {
e := New()

@ -22,7 +22,6 @@ import (
"fmt"
"io"
"log"
"reflect"
"strings"
"time"
@ -30,14 +29,12 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/watch"
)
@ -46,14 +43,7 @@ var ErrNoObjectsVisited = goerrors.New("no objects visited")
// Client represents a client capable of communicating with the Kubernetes API.
type Client struct {
*cmdutil.Factory
// IncludeThirdPartyAPIs indicates whether to load "dynamic" APIs.
//
// This requires additional calls to the Kubernetes API server, and these calls
// are not supported by all versions. Additionally, during testing, initializing
// a client will still attempt to contact a live server. In these situations,
// this flag may need to be disabled.
IncludeThirdPartyAPIs bool
cmdutil.Factory
// Validate idicates whether to load a schema for validation.
Validate bool
// SchemaCacheDir is the path for loading cached schema.
@ -63,10 +53,9 @@ type Client struct {
// New create a new Client
func New(config clientcmd.ClientConfig) *Client {
return &Client{
Factory: cmdutil.NewFactory(config),
IncludeThirdPartyAPIs: true,
Validate: true,
SchemaCacheDir: clientcmd.RecommendedSchemaFile,
Factory: cmdutil.NewFactory(config),
Validate: true,
SchemaCacheDir: clientcmd.RecommendedSchemaFile,
}
}
@ -82,21 +71,15 @@ func (e ErrAlreadyExists) Error() string {
return fmt.Sprintf("Looks like there are no changes for %s", e.errorMsg)
}
// APIClient returns a Kubernetes API client.
//
// This is necessary because cmdutil.Client is a field, not a method, which
// means it can't satisfy an interface's method requirement. In order to ensure
// that an implementation of environment.KubeClient can access the raw API client,
// it is necessary to add this method.
func (c *Client) APIClient() (unversioned.Interface, error) {
return c.Client()
}
// Create creates kubernetes resources from an io.reader
//
// Namespace will set the namespace
func (c *Client) Create(namespace string, reader io.Reader) error {
if err := c.ensureNamespace(namespace); err != nil {
client, err := c.ClientSet()
if err != nil {
return err
}
if err := ensureNamespace(client, namespace); err != nil {
return err
}
return perform(c, namespace, reader, createResource)
@ -107,7 +90,7 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builde
if err != nil {
log.Printf("warning: failed to load schema: %s", err)
}
return c.NewBuilder(c.IncludeThirdPartyAPIs).
return c.NewBuilder().
ContinueOnError().
Schema(schema).
NamespaceParam(namespace).
@ -313,33 +296,22 @@ func deleteResource(info *resource.Info) error {
}
func updateResource(target *resource.Info, currentObj runtime.Object) error {
encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
originalSerialization, err := runtime.Encode(encoder, currentObj)
if err != nil {
return err
}
editedSerialization, err := runtime.Encode(encoder, target.Object)
if err != nil {
return err
}
originalJS, err := yaml.ToJSON(originalSerialization)
original, err := runtime.Encode(encoder, currentObj)
if err != nil {
return err
}
editedJS, err := yaml.ToJSON(editedSerialization)
modified, err := runtime.Encode(encoder, target.Object)
if err != nil {
return err
}
if reflect.DeepEqual(originalJS, editedJS) {
if api.Semantic.DeepEqual(original, modified) {
return ErrAlreadyExists{target.Name}
}
patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, currentObj)
patch, err := strategicpatch.CreateTwoWayMergePatch(original, modified, currentObj)
if err != nil {
return err
}
@ -413,21 +385,6 @@ func waitForJob(e watch.Event, name string) (bool, error) {
return false, nil
}
func (c *Client) ensureNamespace(namespace string) error {
client, err := c.Client()
if err != nil {
return err
}
ns := &api.Namespace{}
ns.Name = namespace
_, err = client.Namespaces().Create(ns)
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}
func deleteUnwantedResources(currentInfos, targetInfos []*resource.Info) {
for _, cInfo := range currentInfos {
if _, ok := findMatchingInfo(cInfo, targetInfos); !ok {

@ -18,51 +18,122 @@ package kube
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
api "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
)
func TestUpdateResource(t *testing.T) {
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
}
tests := []struct {
name string
namespace string
modified *resource.Info
currentObj runtime.Object
err bool
errMessage string
}{
{
name: "no changes when updating resources",
modified: createFakeInfo("nginx", nil),
currentObj: createFakePod("nginx", nil),
err: true,
errMessage: "Looks like there are no changes for nginx",
func newPod(name string) api.Pod {
return api.Pod{
ObjectMeta: api.ObjectMeta{Name: name},
Spec: api.PodSpec{
Containers: []api.Container{{
Name: "app:v4",
Image: "abc/app:v4",
Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}},
}},
},
//{
//name: "valid update input",
//modified: createFakeInfo("nginx", map[string]string{"app": "nginx"}),
//currentObj: createFakePod("nginx", nil),
//},
}
}
func newPodList(names ...string) api.PodList {
var list api.PodList
for _, name := range names {
list.Items = append(list.Items, newPod(name))
}
return list
}
for _, tt := range tests {
err := updateResource(tt.modified, tt.currentObj)
if err != nil && err.Error() != tt.errMessage {
t.Errorf("%q. expected error message: %v, got %v", tt.name, tt.errMessage, err)
func notFoundBody() *unversioned.Status {
return &unversioned.Status{
Code: http.StatusNotFound,
Status: unversioned.StatusFailure,
Reason: unversioned.StatusReasonNotFound,
Message: " \"\" not found",
Details: &unversioned.StatusDetails{},
}
}
func newResponse(code int, obj runtime.Object) (*http.Response, error) {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj))))
return &http.Response{StatusCode: code, Header: header, Body: body}, nil
}
func TestUpdate(t *testing.T) {
listA := newPodList("starfish", "otter", "squid")
listB := newPodList("starfish", "otter", "dolphin")
listB.Items[0].Spec.Containers[0].Ports = []api.ContainerPort{{Name: "https", ContainerPort: 443}}
actions := make(map[string]string)
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
p, m := req.URL.Path, req.Method
actions[p] = m
switch {
case p == "/namespaces/test/pods/starfish" && m == "GET":
return newResponse(200, &listA.Items[0])
case p == "/namespaces/test/pods/otter" && m == "GET":
return newResponse(200, &listA.Items[1])
case p == "/namespaces/test/pods/dolphin" && m == "GET":
return newResponse(404, notFoundBody())
case p == "/namespaces/test/pods/starfish" && m == "PATCH":
data, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("could not dump request: %s", err)
}
req.Body.Close()
expected := `{"spec":{"containers":[{"name":"app:v4","ports":[{"containerPort":443,"name":"https","protocol":"TCP"},{"$patch":"delete","containerPort":80}]}]}}`
if string(data) != expected {
t.Errorf("expected patch %s, got %s", expected, string(data))
}
return newResponse(200, &listB.Items[0])
case p == "/namespaces/test/pods" && m == "POST":
return newResponse(200, &listB.Items[1])
case p == "/namespaces/test/pods/squid" && m == "DELETE":
return newResponse(200, &listB.Items[1])
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
c := &Client{Factory: f}
if err := c.Update("test", objBody(codec, &listA), objBody(codec, &listB)); err != nil {
t.Fatal(err)
}
expectedActions := map[string]string{
"/namespaces/test/pods/dolphin": "GET",
"/namespaces/test/pods/otter": "GET",
"/namespaces/test/pods/starfish": "PATCH",
"/namespaces/test/pods": "POST",
"/namespaces/test/pods/squid": "DELETE",
}
for k, v := range expectedActions {
if m, ok := actions[k]; !ok || m != v {
t.Errorf("expected a %s request to %s", k, v)
}
}
}
@ -110,20 +181,18 @@ func TestPerform(t *testing.T) {
return nil
}
c := New(nil)
c.IncludeThirdPartyAPIs = false
c.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
return &fake.RESTClient{}, nil
}
c.Validator = func(validate bool, cacheDir string) (validation.Schema, error) {
if tt.swaggerFile == "" {
return validation.NullSchema{}, nil
}
f, tf, _, _ := cmdtesting.NewAPIFactory()
c := &Client{Factory: f}
if tt.swaggerFile != "" {
data, err := ioutil.ReadFile(tt.swaggerFile)
if err != nil {
t.Fatalf("could not read swagger spec: %s", err)
}
validator, err := validation.NewSwaggerSchemaFromBytes(data, nil)
if err != nil {
t.Fatalf("could not load swagger spec: %s", err)
}
return validation.NewSwaggerSchemaFromBytes(data, nil)
tf.Validator = validator
}
err := perform(c, tt.namespace, tt.reader, fn)
@ -143,14 +212,12 @@ func TestPerform(t *testing.T) {
func TestReal(t *testing.T) {
t.Skip("This is a live test, comment this line to run")
c := New(nil)
c.IncludeThirdPartyAPIs = false
if err := c.Create("test", strings.NewReader(guestbookManifest)); err != nil {
t.Fatal(err)
}
testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest
c = New(nil)
c.IncludeThirdPartyAPIs = false
if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil {
t.Fatal(err)
}
@ -322,52 +389,3 @@ spec:
ports:
- containerPort: 80
`
func createFakePod(name string, labels map[string]string) runtime.Object {
objectMeta := createObjectMeta(name, labels)
object := &api.Pod{
ObjectMeta: objectMeta,
}
return object
}
func createFakeInfo(name string, labels map[string]string) *resource.Info {
pod := createFakePod(name, labels)
marshaledObj, _ := json.Marshal(pod)
mapping := &meta.RESTMapping{
Resource: name,
Scope: meta.RESTScopeNamespace,
GroupVersionKind: unversioned.GroupVersionKind{
Kind: "Pod",
Version: "v1",
}}
client := &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return &http.Response{
StatusCode: 200,
Header: header,
Body: ioutil.NopCloser(bytes.NewReader(marshaledObj)),
}, nil
})}
info := resource.NewInfo(client, mapping, "default", "nginx", false)
info.Object = pod
return info
}
func createObjectMeta(name string, labels map[string]string) api.ObjectMeta {
objectMeta := api.ObjectMeta{Name: name, Namespace: "default"}
if labels != nil {
objectMeta.Labels = labels
}
return objectMeta
}

@ -0,0 +1,41 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kube // import "k8s.io/helm/pkg/kube"
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
)
func createNamespace(client internalclientset.Interface, namespace string) error {
ns := &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: namespace,
},
}
_, err := client.Core().Namespaces().Create(ns)
return err
}
func ensureNamespace(client internalclientset.Interface, namespace string) error {
err := createNamespace(client, namespace)
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}

@ -1,10 +1,11 @@
/*
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
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,
@ -13,25 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package helmpath
package kube // import "k8s.io/helm/pkg/kube"
import (
"testing"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
func TestHelmHome(t *testing.T) {
hh := Home("/r")
isEq := func(t *testing.T, a, b string) {
if a != b {
t.Errorf("Expected %q, got %q", a, b)
}
func TestEnsureNamespace(t *testing.T) {
client := fake.NewSimpleClientset()
if err := ensureNamespace(client, "foo"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := ensureNamespace(client, "foo"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if _, err := client.Core().Namespaces().Get("foo"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
isEq(t, hh.String(), "/r")
isEq(t, hh.Repository(), "/r/repository")
isEq(t, hh.RepositoryFile(), "/r/repository/repositories.yaml")
isEq(t, hh.LocalRepository(), "/r/repository/local")
isEq(t, hh.Cache(), "/r/repository/cache")
isEq(t, hh.CacheIndex("t"), "/r/repository/cache/t-index.yaml")
isEq(t, hh.Starters(), "/r/starters")
}

@ -38,11 +38,11 @@ type Tunnel struct {
stopChan chan struct{}
readyChan chan struct{}
config *restclient.Config
client *restclient.RESTClient
client restclient.Interface
}
// NewTunnel creates a new tunnel
func NewTunnel(client *restclient.RESTClient, config *restclient.Config, namespace, podName string, remote int) *Tunnel {
func NewTunnel(client restclient.Interface, config *restclient.Config, namespace, podName string, remote int) *Tunnel {
return &Tunnel{
config: config,
client: client,

@ -2,7 +2,7 @@
{{/*
Expand the name of the chart.
*/}}
{{define "name"}}{{default "nginx" .Values.nameOverride | trunc 24 }}{{end}}
{{define "name"}}{{default "nginx" .Values.nameOverride | trunc 24 | trimSuffix "-" }}{{end}}
{{/*
Create a default fully qualified app name.
@ -12,5 +12,5 @@ We truncate at 24 chars because some Kubernetes name fields are limited to this
*/}}
{{define "fullname"}}
{{- $name := default "nginx" .Values.nameOverride -}}
{{printf "%s-%s" .Release.Name $name | trunc 24 -}}
{{printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{end}}

@ -27,6 +27,8 @@ const (
Status_SUPERSEDED Status_Code = 3
// Status_FAILED indicates that the release was not successfully deployed.
Status_FAILED Status_Code = 4
// Status_DELETING indicates that a delete operation is underway.
Status_DELETING Status_Code = 5
)
var Status_Code_name = map[int32]string{
@ -35,6 +37,7 @@ var Status_Code_name = map[int32]string{
2: "DELETED",
3: "SUPERSEDED",
4: "FAILED",
5: "DELETING",
}
var Status_Code_value = map[string]int32{
"UNKNOWN": 0,
@ -42,6 +45,7 @@ var Status_Code_value = map[string]int32{
"DELETED": 2,
"SUPERSEDED": 3,
"FAILED": 4,
"DELETING": 5,
}
func (x Status_Code) String() string {
@ -79,22 +83,22 @@ func init() {
func init() { proto.RegisterFile("hapi/release/status.proto", fileDescriptor3) }
var fileDescriptor3 = []byte{
// 261 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0xc1, 0x4e, 0x83, 0x40,
0x10, 0x86, 0xdd, 0x16, 0x41, 0xa6, 0x4d, 0x43, 0x36, 0x3d, 0x80, 0xf1, 0x40, 0x7a, 0xe2, 0xe2,
0x92, 0xd4, 0x27, 0xa8, 0xee, 0x9a, 0xa8, 0x84, 0x36, 0x60, 0x63, 0xf4, 0x46, 0xcb, 0x58, 0x9b,
0x10, 0xb6, 0x61, 0x97, 0x43, 0x9f, 0xd8, 0xd7, 0x30, 0x2c, 0x6d, 0xec, 0x71, 0xe6, 0xfb, 0x66,
0xfe, 0x19, 0x08, 0x7e, 0x8a, 0xc3, 0x3e, 0x6e, 0xb0, 0xc2, 0x42, 0x61, 0xac, 0x74, 0xa1, 0x5b,
0xc5, 0x0e, 0x8d, 0xd4, 0x92, 0x8e, 0x3b, 0xc4, 0x4e, 0xe8, 0x36, 0xd8, 0x49, 0xb9, 0xab, 0x30,
0x36, 0x6c, 0xd3, 0x7e, 0xc7, 0x45, 0x7d, 0xec, 0xc5, 0xd9, 0x2f, 0x01, 0x3b, 0x37, 0x93, 0xf4,
0x1e, 0xac, 0xad, 0x2c, 0xd1, 0x27, 0x21, 0x89, 0x26, 0xf3, 0x80, 0x5d, 0xae, 0x60, 0xbd, 0xc3,
0x9e, 0x64, 0x89, 0x99, 0xd1, 0x28, 0x03, 0xa7, 0x44, 0x5d, 0xec, 0x2b, 0xe5, 0x0f, 0x42, 0x12,
0x8d, 0xe6, 0x53, 0xd6, 0xc7, 0xb0, 0x73, 0x0c, 0x5b, 0xd4, 0xc7, 0xec, 0x2c, 0xd1, 0x3b, 0x70,
0x1b, 0x54, 0xb2, 0x6d, 0xb6, 0xa8, 0xfc, 0x61, 0x48, 0x22, 0x37, 0xfb, 0x6f, 0xd0, 0x29, 0x5c,
0xd7, 0x52, 0xa3, 0xf2, 0x2d, 0x43, 0xfa, 0x62, 0xf6, 0x0a, 0x56, 0x97, 0x48, 0x47, 0xe0, 0xac,
0xd3, 0xb7, 0x74, 0xf9, 0x91, 0x7a, 0x57, 0x74, 0x0c, 0x37, 0x5c, 0xac, 0x92, 0xe5, 0xa7, 0xe0,
0x1e, 0xe9, 0x10, 0x17, 0x89, 0x78, 0x17, 0xdc, 0x1b, 0xd0, 0x09, 0x40, 0xbe, 0x5e, 0x89, 0x2c,
0x17, 0x5c, 0x70, 0x6f, 0x48, 0x01, 0xec, 0xe7, 0xc5, 0x4b, 0x22, 0xb8, 0x67, 0x3d, 0xba, 0x5f,
0xce, 0xe9, 0x99, 0x8d, 0x6d, 0x2e, 0x7c, 0xf8, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xae, 0x07, 0x47,
0x1f, 0x41, 0x01, 0x00, 0x00,
// 269 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6f, 0x82, 0x40,
0x10, 0x86, 0xbb, 0x8a, 0x50, 0x46, 0x63, 0x36, 0x1b, 0x0f, 0xd0, 0xf4, 0x40, 0x3c, 0x71, 0xe9,
0x92, 0xd8, 0x5f, 0x60, 0xbb, 0xdb, 0xc6, 0x94, 0xa0, 0x01, 0x4d, 0x3f, 0x6e, 0x28, 0x53, 0x6b,
0x42, 0x58, 0xc3, 0xc2, 0xc1, 0x1f, 0xde, 0x7b, 0x03, 0x68, 0xea, 0x71, 0xf7, 0x79, 0xde, 0x79,
0x67, 0xc0, 0xfd, 0x49, 0x8f, 0x87, 0xa0, 0xc4, 0x1c, 0x53, 0x8d, 0x81, 0xae, 0xd2, 0xaa, 0xd6,
0xfc, 0x58, 0xaa, 0x4a, 0xb1, 0x51, 0x83, 0xf8, 0x19, 0xdd, 0xb9, 0x7b, 0xa5, 0xf6, 0x39, 0x06,
0x2d, 0xdb, 0xd6, 0xdf, 0x41, 0x5a, 0x9c, 0x3a, 0x71, 0xfa, 0x4b, 0xc0, 0x4c, 0xda, 0x24, 0x7b,
0x00, 0x63, 0xa7, 0x32, 0x74, 0x88, 0x47, 0xfc, 0xf1, 0xcc, 0xe5, 0xd7, 0x23, 0x78, 0xe7, 0xf0,
0x67, 0x95, 0x61, 0xdc, 0x6a, 0x8c, 0x83, 0x95, 0x61, 0x95, 0x1e, 0x72, 0xed, 0xf4, 0x3c, 0xe2,
0x0f, 0x67, 0x13, 0xde, 0xd5, 0xf0, 0x4b, 0x0d, 0x9f, 0x17, 0xa7, 0xf8, 0x22, 0xb1, 0x7b, 0xb0,
0x4b, 0xd4, 0xaa, 0x2e, 0x77, 0xa8, 0x9d, 0xbe, 0x47, 0x7c, 0x3b, 0xfe, 0xff, 0x60, 0x13, 0x18,
0x14, 0xaa, 0x42, 0xed, 0x18, 0x2d, 0xe9, 0x1e, 0xd3, 0x0f, 0x30, 0x9a, 0x46, 0x36, 0x04, 0x6b,
0x13, 0xbd, 0x45, 0xcb, 0xf7, 0x88, 0xde, 0xb0, 0x11, 0xdc, 0x0a, 0xb9, 0x0a, 0x97, 0x9f, 0x52,
0x50, 0xd2, 0x20, 0x21, 0x43, 0xb9, 0x96, 0x82, 0xf6, 0xd8, 0x18, 0x20, 0xd9, 0xac, 0x64, 0x9c,
0x48, 0x21, 0x05, 0xed, 0x33, 0x00, 0xf3, 0x65, 0xbe, 0x08, 0xa5, 0xa0, 0x46, 0x17, 0x0b, 0xe5,
0x7a, 0x11, 0xbd, 0xd2, 0xc1, 0x93, 0xfd, 0x65, 0x9d, 0x4f, 0xdb, 0x9a, 0xed, 0xbe, 0x8f, 0x7f,
0x01, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x7b, 0x5f, 0x3b, 0x4f, 0x01, 0x00, 0x00,
}

@ -394,6 +394,8 @@ func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescri
type UninstallReleaseResponse struct {
// Release is the release that was marked deleted.
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
// Info is an uninstall message
Info string `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"`
}
func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} }
@ -418,7 +420,7 @@ func (*GetVersionRequest) ProtoMessage() {}
func (*GetVersionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
type GetVersionResponse struct {
Version *hapi_version.Version `protobuf:"bytes,1,opt,name=Version" json:"Version,omitempty"`
Version *hapi_version.Version `protobuf:"bytes,1,opt,name=Version,json=version" json:"Version,omitempty"`
}
func (m *GetVersionResponse) Reset() { *m = GetVersionResponse{} }
@ -878,68 +880,69 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 1004 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdf, 0x6f, 0xe3, 0xc4,
0x13, 0xaf, 0x93, 0x34, 0x3f, 0xa6, 0x3f, 0xbe, 0xe9, 0x5e, 0xda, 0xb8, 0xd6, 0x17, 0x14, 0x19,
0xc1, 0x85, 0x83, 0x4b, 0x21, 0x3c, 0x21, 0x21, 0xa4, 0x5e, 0x2e, 0x4a, 0xcb, 0x95, 0x9c, 0xb4,
0xa1, 0x20, 0xf1, 0x40, 0xe4, 0x26, 0x9b, 0xab, 0x39, 0xc7, 0x1b, 0xbc, 0x9b, 0xea, 0xf2, 0xce,
0x0b, 0xff, 0x06, 0xff, 0x07, 0xff, 0x13, 0xef, 0xbc, 0x20, 0xef, 0x0f, 0x37, 0x76, 0xec, 0x9c,
0xc9, 0x8b, 0xed, 0xdd, 0x99, 0xfd, 0xcc, 0xcc, 0x67, 0x76, 0x66, 0x12, 0xb0, 0xee, 0x9d, 0x85,
0x7b, 0xc1, 0x48, 0xf0, 0xe0, 0x4e, 0x08, 0xbb, 0xe0, 0xae, 0xe7, 0x91, 0xa0, 0xb3, 0x08, 0x28,
0xa7, 0xa8, 0x11, 0xca, 0x3a, 0x5a, 0xd6, 0x91, 0x32, 0xeb, 0x4c, 0x9c, 0x98, 0xdc, 0x3b, 0x01,
0x97, 0x4f, 0xa9, 0x6d, 0x35, 0xd7, 0xf7, 0xa9, 0x3f, 0x73, 0xdf, 0x28, 0x81, 0x34, 0x11, 0x10,
0x8f, 0x38, 0x8c, 0xe8, 0x77, 0xec, 0x90, 0x96, 0xb9, 0xfe, 0x8c, 0x2a, 0xc1, 0x79, 0x4c, 0xc0,
0xb8, 0xc3, 0x97, 0x2c, 0x86, 0xf7, 0x40, 0x02, 0xe6, 0x52, 0x5f, 0xbf, 0xa5, 0xcc, 0xfe, 0xb3,
0x00, 0x4f, 0x6e, 0x5c, 0xc6, 0xb1, 0x3c, 0xc8, 0x30, 0xf9, 0x6d, 0x49, 0x18, 0x47, 0x0d, 0xd8,
0xf7, 0xdc, 0xb9, 0xcb, 0x4d, 0xa3, 0x65, 0xb4, 0x8b, 0x58, 0x2e, 0xd0, 0x19, 0x94, 0xe9, 0x6c,
0xc6, 0x08, 0x37, 0x0b, 0x2d, 0xa3, 0x5d, 0xc3, 0x6a, 0x85, 0xbe, 0x85, 0x0a, 0xa3, 0x01, 0x1f,
0xdf, 0xad, 0xcc, 0x62, 0xcb, 0x68, 0x1f, 0x77, 0x3f, 0xee, 0xa4, 0x51, 0xd1, 0x09, 0x2d, 0x8d,
0x68, 0xc0, 0x3b, 0xe1, 0xe3, 0xc5, 0x0a, 0x97, 0x99, 0x78, 0x87, 0xb8, 0x33, 0xd7, 0xe3, 0x24,
0x30, 0x4b, 0x12, 0x57, 0xae, 0xd0, 0x00, 0x40, 0xe0, 0xd2, 0x60, 0x4a, 0x02, 0x73, 0x5f, 0x40,
0xb7, 0x73, 0x40, 0xbf, 0x0e, 0xf5, 0x71, 0x8d, 0xe9, 0x4f, 0xf4, 0x0d, 0x1c, 0x4a, 0x4a, 0xc6,
0x13, 0x3a, 0x25, 0xcc, 0x2c, 0xb7, 0x8a, 0xed, 0xe3, 0xee, 0xb9, 0x84, 0xd2, 0x0c, 0x8f, 0x24,
0x69, 0x3d, 0x3a, 0x25, 0xf8, 0x40, 0xaa, 0x87, 0xdf, 0xcc, 0xfe, 0x05, 0xaa, 0x1a, 0xde, 0xee,
0x42, 0x59, 0x3a, 0x8f, 0x0e, 0xa0, 0x72, 0x3b, 0x7c, 0x35, 0x7c, 0xfd, 0xd3, 0xb0, 0xbe, 0x87,
0xaa, 0x50, 0x1a, 0x5e, 0x7e, 0xdf, 0xaf, 0x1b, 0xe8, 0x04, 0x8e, 0x6e, 0x2e, 0x47, 0x3f, 0x8c,
0x71, 0xff, 0xa6, 0x7f, 0x39, 0xea, 0xbf, 0xac, 0x17, 0xec, 0x0f, 0xa1, 0x16, 0x79, 0x85, 0x2a,
0x50, 0xbc, 0x1c, 0xf5, 0xe4, 0x91, 0x97, 0xfd, 0x51, 0xaf, 0x6e, 0xd8, 0x7f, 0x18, 0xd0, 0x88,
0x27, 0x81, 0x2d, 0xa8, 0xcf, 0x48, 0x98, 0x85, 0x09, 0x5d, 0xfa, 0x51, 0x16, 0xc4, 0x02, 0x21,
0x28, 0xf9, 0xe4, 0x9d, 0xce, 0x81, 0xf8, 0x0e, 0x35, 0x39, 0xe5, 0x8e, 0x27, 0xf8, 0x2f, 0x62,
0xb9, 0x40, 0x5f, 0x42, 0x55, 0x05, 0xc7, 0xcc, 0x52, 0xab, 0xd8, 0x3e, 0xe8, 0x9e, 0xc6, 0x43,
0x56, 0x16, 0x71, 0xa4, 0x66, 0x0f, 0xa0, 0x39, 0x20, 0xda, 0x13, 0xc9, 0x88, 0xbe, 0x13, 0xa1,
0x5d, 0x67, 0x4e, 0x84, 0x33, 0xa1, 0x5d, 0x67, 0x4e, 0x90, 0x09, 0x15, 0x75, 0xa1, 0x84, 0x3b,
0xfb, 0x58, 0x2f, 0x6d, 0x0e, 0xe6, 0x26, 0x90, 0x8a, 0x2b, 0x0d, 0xe9, 0x13, 0x28, 0x85, 0xd7,
0x59, 0xc0, 0x1c, 0x74, 0x51, 0xdc, 0xcf, 0x6b, 0x7f, 0x46, 0xb1, 0x90, 0xa3, 0xff, 0x43, 0x2d,
0xd4, 0x67, 0x0b, 0x67, 0x42, 0x44, 0xb4, 0x35, 0xfc, 0xb8, 0x61, 0x5f, 0xad, 0x5b, 0xed, 0x51,
0x9f, 0x13, 0x9f, 0xef, 0xe6, 0xff, 0x0d, 0x9c, 0xa7, 0x20, 0xa9, 0x00, 0x2e, 0xa0, 0xa2, 0x5c,
0x13, 0x68, 0x99, 0xbc, 0x6a, 0x2d, 0xfb, 0x2f, 0x03, 0x1a, 0xb7, 0x8b, 0xa9, 0xc3, 0x89, 0x16,
0x6d, 0x71, 0xea, 0x29, 0xec, 0x8b, 0xb6, 0xa0, 0xb8, 0x38, 0x91, 0xd8, 0xb2, 0x77, 0xf4, 0xc2,
0x27, 0x96, 0x72, 0xf4, 0x0c, 0xca, 0x0f, 0x8e, 0xb7, 0x24, 0x4c, 0x10, 0x11, 0xb1, 0xa6, 0x34,
0x45, 0x4f, 0xc1, 0x4a, 0x03, 0x35, 0xa1, 0x32, 0x0d, 0x56, 0xe3, 0x60, 0xe9, 0x8b, 0x22, 0xab,
0xe2, 0xf2, 0x34, 0x58, 0xe1, 0xa5, 0x8f, 0x3e, 0x82, 0xa3, 0xa9, 0xcb, 0x9c, 0x3b, 0x8f, 0x8c,
0xef, 0x29, 0x7d, 0xcb, 0x44, 0x9d, 0x55, 0xf1, 0xa1, 0xda, 0xbc, 0x0a, 0xf7, 0xec, 0x2b, 0x38,
0x4d, 0xb8, 0xbf, 0x2b, 0x13, 0xbf, 0x1b, 0x70, 0x86, 0xa9, 0xe7, 0xdd, 0x39, 0x93, 0xb7, 0x39,
0xb8, 0x58, 0x73, 0xbb, 0xb0, 0xdd, 0xed, 0xe2, 0xa6, 0xdb, 0xeb, 0xe9, 0x2d, 0xc5, 0xd3, 0xfb,
0x1d, 0x34, 0x37, 0xbc, 0xd8, 0x35, 0xa4, 0x7f, 0x0c, 0x38, 0xbd, 0xf6, 0x19, 0x77, 0x3c, 0x2f,
0x11, 0x51, 0x94, 0x49, 0x23, 0x77, 0x26, 0x0b, 0xff, 0x25, 0x93, 0xc5, 0x18, 0x25, 0x9a, 0xbf,
0xd2, 0x1a, 0x7f, 0x79, 0xb2, 0x1b, 0xaf, 0xa9, 0x72, 0xa2, 0xa6, 0xd0, 0x07, 0x00, 0x01, 0x59,
0x32, 0x32, 0x16, 0xe0, 0x15, 0x71, 0xbe, 0x26, 0x76, 0x86, 0xce, 0x9c, 0xd8, 0xd7, 0x70, 0x96,
0x0c, 0x7e, 0x57, 0x22, 0xef, 0xa1, 0x79, 0xeb, 0xbb, 0xa9, 0x4c, 0xa6, 0xdd, 0x8d, 0x8d, 0xd8,
0x0a, 0x29, 0xb1, 0x35, 0x60, 0x7f, 0xb1, 0x0c, 0xde, 0x10, 0xc5, 0x95, 0x5c, 0xd8, 0xaf, 0xc0,
0xdc, 0xb4, 0xb4, 0xab, 0xdb, 0x4f, 0xe0, 0x64, 0x40, 0xf8, 0x8f, 0xf2, 0x66, 0x29, 0x87, 0xed,
0x3e, 0xa0, 0xf5, 0xcd, 0x47, 0x6c, 0xb5, 0x15, 0xc7, 0xd6, 0x53, 0x59, 0xeb, 0x6b, 0x2d, 0xfb,
0x6b, 0x81, 0x7d, 0xe5, 0x32, 0x4e, 0x83, 0xd5, 0x36, 0x32, 0xea, 0x50, 0x9c, 0x3b, 0xef, 0x54,
0x17, 0x0b, 0x3f, 0xed, 0x81, 0xf0, 0x20, 0x3a, 0xaa, 0x3c, 0x58, 0x9f, 0x09, 0x46, 0xae, 0x99,
0xd0, 0xfd, 0xbb, 0x02, 0xc7, 0xba, 0x91, 0xcb, 0xb1, 0x8b, 0x5c, 0x38, 0x5c, 0x9f, 0x58, 0xe8,
0xd3, 0xec, 0xa9, 0x9c, 0xf8, 0x69, 0x61, 0x3d, 0xcb, 0xa3, 0x2a, 0x9d, 0xb5, 0xf7, 0xbe, 0x30,
0x10, 0x83, 0x7a, 0x72, 0x90, 0xa0, 0xe7, 0xe9, 0x18, 0x19, 0x93, 0xcb, 0xea, 0xe4, 0x55, 0xd7,
0x66, 0xd1, 0x83, 0xa0, 0x3d, 0xde, 0xfd, 0xd1, 0x7b, 0x61, 0xe2, 0x03, 0xc7, 0xba, 0xc8, 0xad,
0x1f, 0xd9, 0xfd, 0x15, 0x8e, 0x62, 0x7d, 0x16, 0x65, 0xb0, 0x95, 0x36, 0x4b, 0xac, 0xcf, 0x72,
0xe9, 0x46, 0xb6, 0xe6, 0x70, 0x1c, 0x2f, 0x5c, 0x94, 0x01, 0x90, 0xda, 0xdb, 0xac, 0xcf, 0xf3,
0x29, 0x47, 0xe6, 0x18, 0xd4, 0x93, 0x25, 0x97, 0x95, 0xc7, 0x8c, 0x26, 0x90, 0x95, 0xc7, 0xac,
0x4a, 0xb6, 0xf7, 0x90, 0x03, 0xf0, 0x58, 0x85, 0xe8, 0x69, 0x66, 0x42, 0xe2, 0xc5, 0x6b, 0xb5,
0xdf, 0xaf, 0x18, 0x99, 0x58, 0xc0, 0xff, 0x12, 0x93, 0x04, 0x65, 0x50, 0x93, 0x3e, 0xf6, 0xac,
0xe7, 0x39, 0xb5, 0x13, 0x41, 0xa9, 0xc2, 0xde, 0x12, 0x54, 0xbc, 0x6b, 0x6c, 0x09, 0x2a, 0xd1,
0x23, 0xec, 0xbd, 0x17, 0xf0, 0x73, 0x55, 0xeb, 0xdd, 0x95, 0xc5, 0x5f, 0x85, 0xaf, 0xfe, 0x0d,
0x00, 0x00, 0xff, 0xff, 0x9a, 0xb0, 0xe9, 0x29, 0xfb, 0x0c, 0x00, 0x00,
// 1010 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0x5f, 0x6f, 0xe3, 0x44,
0x10, 0xaf, 0x93, 0x34, 0x7f, 0xa6, 0x7f, 0x48, 0xf7, 0xd2, 0xc6, 0xb5, 0x00, 0x45, 0x46, 0x70,
0xe1, 0xe0, 0x52, 0x08, 0x4f, 0x48, 0x08, 0xa9, 0x97, 0x8b, 0xd2, 0x42, 0xc9, 0x49, 0x1b, 0x0a,
0x12, 0x0f, 0x44, 0x6e, 0xb2, 0xb9, 0x9a, 0x73, 0xbc, 0xc1, 0xbb, 0xa9, 0x2e, 0xef, 0xbc, 0xf0,
0x35, 0xf8, 0x1e, 0x7c, 0x27, 0xde, 0x79, 0x41, 0xde, 0x3f, 0x6e, 0xec, 0xd8, 0x39, 0x93, 0x17,
0xdb, 0xbb, 0x33, 0x3b, 0xf3, 0x9b, 0xdf, 0xec, 0xcc, 0x24, 0x60, 0xdd, 0x3b, 0x0b, 0xf7, 0x82,
0x91, 0xe0, 0xc1, 0x9d, 0x10, 0x76, 0xc1, 0x5d, 0xcf, 0x23, 0x41, 0x67, 0x11, 0x50, 0x4e, 0x51,
0x23, 0x94, 0x75, 0xb4, 0xac, 0x23, 0x65, 0xd6, 0x99, 0x38, 0x31, 0xb9, 0x77, 0x02, 0x2e, 0x9f,
0x52, 0xdb, 0x6a, 0xae, 0xef, 0x53, 0x7f, 0xe6, 0xbe, 0x56, 0x02, 0xe9, 0x22, 0x20, 0x1e, 0x71,
0x18, 0xd1, 0xef, 0xd8, 0x21, 0x2d, 0x73, 0xfd, 0x19, 0x55, 0x82, 0xf3, 0x98, 0x80, 0x71, 0x87,
0x2f, 0x59, 0xcc, 0xde, 0x03, 0x09, 0x98, 0x4b, 0x7d, 0xfd, 0x96, 0x32, 0xfb, 0xaf, 0x02, 0x3c,
0xb9, 0x71, 0x19, 0xc7, 0xf2, 0x20, 0xc3, 0xe4, 0xf7, 0x25, 0x61, 0x1c, 0x35, 0x60, 0xdf, 0x73,
0xe7, 0x2e, 0x37, 0x8d, 0x96, 0xd1, 0x2e, 0x62, 0xb9, 0x40, 0x67, 0x50, 0xa6, 0xb3, 0x19, 0x23,
0xdc, 0x2c, 0xb4, 0x8c, 0x76, 0x0d, 0xab, 0x15, 0xfa, 0x16, 0x2a, 0x8c, 0x06, 0x7c, 0x7c, 0xb7,
0x32, 0x8b, 0x2d, 0xa3, 0x7d, 0xdc, 0xfd, 0xb8, 0x93, 0x46, 0x45, 0x27, 0xf4, 0x34, 0xa2, 0x01,
0xef, 0x84, 0x8f, 0x17, 0x2b, 0x5c, 0x66, 0xe2, 0x1d, 0xda, 0x9d, 0xb9, 0x1e, 0x27, 0x81, 0x59,
0x92, 0x76, 0xe5, 0x0a, 0x0d, 0x00, 0x84, 0x5d, 0x1a, 0x4c, 0x49, 0x60, 0xee, 0x0b, 0xd3, 0xed,
0x1c, 0xa6, 0x5f, 0x85, 0xfa, 0xb8, 0xc6, 0xf4, 0x27, 0xfa, 0x06, 0x0e, 0x25, 0x25, 0xe3, 0x09,
0x9d, 0x12, 0x66, 0x96, 0x5b, 0xc5, 0xf6, 0x71, 0xf7, 0x5c, 0x9a, 0xd2, 0x0c, 0x8f, 0x24, 0x69,
0x3d, 0x3a, 0x25, 0xf8, 0x40, 0xaa, 0x87, 0xdf, 0xcc, 0xfe, 0x15, 0xaa, 0xda, 0xbc, 0xdd, 0x85,
0xb2, 0x04, 0x8f, 0x0e, 0xa0, 0x72, 0x3b, 0xfc, 0x7e, 0xf8, 0xea, 0xe7, 0x61, 0x7d, 0x0f, 0x55,
0xa1, 0x34, 0xbc, 0xfc, 0xa1, 0x5f, 0x37, 0xd0, 0x09, 0x1c, 0xdd, 0x5c, 0x8e, 0x7e, 0x1c, 0xe3,
0xfe, 0x4d, 0xff, 0x72, 0xd4, 0x7f, 0x59, 0x2f, 0xd8, 0x1f, 0x42, 0x2d, 0x42, 0x85, 0x2a, 0x50,
0xbc, 0x1c, 0xf5, 0xe4, 0x91, 0x97, 0xfd, 0x51, 0xaf, 0x6e, 0xd8, 0x7f, 0x1a, 0xd0, 0x88, 0x27,
0x81, 0x2d, 0xa8, 0xcf, 0x48, 0x98, 0x85, 0x09, 0x5d, 0xfa, 0x51, 0x16, 0xc4, 0x02, 0x21, 0x28,
0xf9, 0xe4, 0xad, 0xce, 0x81, 0xf8, 0x0e, 0x35, 0x39, 0xe5, 0x8e, 0x27, 0xf8, 0x2f, 0x62, 0xb9,
0x40, 0x5f, 0x42, 0x55, 0x05, 0xc7, 0xcc, 0x52, 0xab, 0xd8, 0x3e, 0xe8, 0x9e, 0xc6, 0x43, 0x56,
0x1e, 0x71, 0xa4, 0x66, 0x0f, 0xa0, 0x39, 0x20, 0x1a, 0x89, 0x64, 0x44, 0xdf, 0x89, 0xd0, 0xaf,
0x33, 0x27, 0x02, 0x4c, 0xe8, 0xd7, 0x99, 0x13, 0x64, 0x42, 0x45, 0x5d, 0x28, 0x01, 0x67, 0x1f,
0xeb, 0xa5, 0xcd, 0xc1, 0xdc, 0x34, 0xa4, 0xe2, 0x4a, 0xb3, 0xf4, 0x09, 0x94, 0xc2, 0xeb, 0x2c,
0xcc, 0x1c, 0x74, 0x51, 0x1c, 0xe7, 0xb5, 0x3f, 0xa3, 0x58, 0xc8, 0xd1, 0xfb, 0x50, 0x0b, 0xf5,
0xd9, 0xc2, 0x99, 0x10, 0x11, 0x6d, 0x0d, 0x3f, 0x6e, 0xd8, 0x57, 0xeb, 0x5e, 0x7b, 0xd4, 0xe7,
0xc4, 0xe7, 0xbb, 0xe1, 0xbf, 0x81, 0xf3, 0x14, 0x4b, 0x2a, 0x80, 0x0b, 0xa8, 0x28, 0x68, 0xc2,
0x5a, 0x26, 0xaf, 0x5a, 0xcb, 0xfe, 0xdb, 0x80, 0xc6, 0xed, 0x62, 0xea, 0x70, 0xa2, 0x45, 0x5b,
0x40, 0x3d, 0x85, 0x7d, 0xd1, 0x16, 0x14, 0x17, 0x27, 0xd2, 0xb6, 0xec, 0x1d, 0xbd, 0xf0, 0x89,
0xa5, 0x1c, 0x3d, 0x83, 0xf2, 0x83, 0xe3, 0x2d, 0x09, 0x13, 0x44, 0x44, 0xac, 0x29, 0x4d, 0xd1,
0x53, 0xb0, 0xd2, 0x40, 0x4d, 0xa8, 0x4c, 0x83, 0xd5, 0x38, 0x58, 0xfa, 0xa2, 0xc8, 0xaa, 0xb8,
0x3c, 0x0d, 0x56, 0x78, 0xe9, 0xa3, 0x8f, 0xe0, 0x68, 0xea, 0x32, 0xe7, 0xce, 0x23, 0xe3, 0x7b,
0x4a, 0xdf, 0x30, 0x51, 0x67, 0x55, 0x7c, 0xa8, 0x36, 0xaf, 0xc2, 0x3d, 0xfb, 0x0a, 0x4e, 0x13,
0xf0, 0x77, 0x65, 0xe2, 0x0f, 0x03, 0xce, 0x30, 0xf5, 0xbc, 0x3b, 0x67, 0xf2, 0x26, 0x07, 0x17,
0x6b, 0xb0, 0x0b, 0xdb, 0x61, 0x17, 0x37, 0x61, 0xaf, 0xa7, 0xb7, 0x14, 0x4f, 0xef, 0x77, 0xd0,
0xdc, 0x40, 0xb1, 0x6b, 0x48, 0xff, 0x1a, 0x70, 0x7a, 0xed, 0x33, 0xee, 0x78, 0x5e, 0x22, 0xa2,
0x28, 0x93, 0x46, 0xee, 0x4c, 0x16, 0xfe, 0x4f, 0x26, 0x8b, 0x31, 0x4a, 0x34, 0x7f, 0xa5, 0x35,
0xfe, 0xf2, 0x64, 0x37, 0x5e, 0x53, 0xe5, 0x44, 0x4d, 0xa1, 0x0f, 0x00, 0x02, 0xb2, 0x64, 0x64,
0x2c, 0x8c, 0x57, 0xc4, 0xf9, 0x9a, 0xd8, 0x19, 0x3a, 0x73, 0x62, 0x5f, 0xc3, 0x59, 0x32, 0xf8,
0x5d, 0x89, 0xbc, 0x87, 0xe6, 0xad, 0xef, 0xa6, 0x32, 0x99, 0x76, 0x37, 0x36, 0x62, 0x2b, 0xa4,
0xc4, 0xd6, 0x80, 0xfd, 0xc5, 0x32, 0x78, 0x4d, 0x14, 0x57, 0x72, 0x61, 0x8f, 0xc1, 0xdc, 0xf4,
0xb4, 0x23, 0xec, 0x10, 0x5b, 0xd4, 0xba, 0x6a, 0xb2, 0x4d, 0xd9, 0x4f, 0xe0, 0x64, 0x40, 0xf8,
0x4f, 0xf2, 0xb6, 0xa9, 0x20, 0xec, 0x3e, 0xa0, 0xf5, 0xcd, 0x47, 0x7f, 0x6a, 0x2b, 0xee, 0x4f,
0x4f, 0x6a, 0xad, 0x1f, 0xdd, 0xdd, 0xaf, 0x85, 0xed, 0x2b, 0x97, 0x71, 0x1a, 0xac, 0xb6, 0x11,
0x54, 0x87, 0xe2, 0xdc, 0x79, 0xab, 0x3a, 0x5b, 0xf8, 0x69, 0x0f, 0x04, 0x82, 0xe8, 0xa8, 0x42,
0xb0, 0x3e, 0x27, 0x8c, 0x5c, 0x73, 0xa2, 0xfb, 0x4f, 0x05, 0x8e, 0x75, 0x73, 0x97, 0xa3, 0x18,
0xb9, 0x70, 0xb8, 0x3e, 0xc5, 0xd0, 0xa7, 0xd9, 0x93, 0x3a, 0xf1, 0x73, 0xc3, 0x7a, 0x96, 0x47,
0x55, 0x82, 0xb5, 0xf7, 0xbe, 0x30, 0x10, 0x83, 0x7a, 0x72, 0xb8, 0xa0, 0xe7, 0xe9, 0x36, 0x32,
0xa6, 0x99, 0xd5, 0xc9, 0xab, 0xae, 0xdd, 0xa2, 0x07, 0x41, 0x7b, 0x7c, 0x22, 0xa0, 0x77, 0x9a,
0x89, 0x0f, 0x21, 0xeb, 0x22, 0xb7, 0x7e, 0xe4, 0xf7, 0x37, 0x38, 0x8a, 0xf5, 0x5e, 0x94, 0xc1,
0x56, 0xda, 0x7c, 0xb1, 0x3e, 0xcb, 0xa5, 0x1b, 0xf9, 0x9a, 0xc3, 0x71, 0xbc, 0x98, 0x51, 0x86,
0x81, 0xd4, 0x7e, 0x67, 0x7d, 0x9e, 0x4f, 0x39, 0x72, 0xc7, 0xa0, 0x9e, 0x2c, 0xc3, 0xac, 0x3c,
0x66, 0x34, 0x86, 0xac, 0x3c, 0x66, 0x55, 0xb7, 0xbd, 0x87, 0x1c, 0x80, 0xc7, 0x2a, 0x44, 0x4f,
0x33, 0x13, 0x12, 0x2f, 0x5e, 0xab, 0xfd, 0x6e, 0xc5, 0xc8, 0xc5, 0x02, 0xde, 0x4b, 0x4c, 0x17,
0x94, 0x41, 0x4d, 0xfa, 0x28, 0xb4, 0x9e, 0xe7, 0xd4, 0x4e, 0x04, 0xa5, 0x0a, 0x7b, 0x4b, 0x50,
0xf1, 0xae, 0xb1, 0x25, 0xa8, 0x44, 0x8f, 0xb0, 0xf7, 0x5e, 0xc0, 0x2f, 0x55, 0xad, 0x77, 0x57,
0x16, 0x7f, 0x1f, 0xbe, 0xfa, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xa9, 0xd1, 0x0e, 0x5d, 0x0f, 0x0d,
0x00, 0x00,
}

@ -168,8 +168,10 @@ type PassphraseFetcher func(name string) ([]byte, error)
//
// If the key is successfully unlocked, it will return nil.
func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
if s.Entity == nil || s.Entity.PrivateKey == nil {
if s.Entity == nil {
return errors.New("private key not found")
} else if s.Entity.PrivateKey == nil {
return errors.New("provided key is not a private key")
}
// Nothing else to do if key is not encrypted.
@ -200,8 +202,10 @@ func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
// The Signatory must have a valid Entity.PrivateKey for this to work. If it does
// not, an error will be returned.
func (s *Signatory) ClearSign(chartpath string) (string, error) {
if s.Entity == nil || s.Entity.PrivateKey == nil {
if s.Entity == nil {
return "", errors.New("private key not found")
} else if s.Entity.PrivateKey == nil {
return "", errors.New("provided key is not a private key")
}
if fi, err := os.Stat(chartpath); err != nil {

@ -27,13 +27,12 @@ import (
"time"
"github.com/golang/protobuf/proto"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/kubernetes/pkg/api"
kberrs "k8s.io/kubernetes/pkg/api/errors"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
kblabels "k8s.io/kubernetes/pkg/labels"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
var _ Driver = (*ConfigMaps)(nil)
@ -48,12 +47,12 @@ var magicGzip = []byte{0x1f, 0x8b, 0x08}
// ConfigMaps is a wrapper around an implementation of a kubernetes
// ConfigMapsInterface.
type ConfigMaps struct {
impl client.ConfigMapsInterface
impl internalversion.ConfigMapInterface
}
// NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of
// the kubernetes ConfigMapsInterface.
func NewConfigMaps(impl client.ConfigMapsInterface) *ConfigMaps {
func NewConfigMaps(impl internalversion.ConfigMapInterface) *ConfigMaps {
return &ConfigMaps{impl: impl}
}
@ -210,7 +209,7 @@ func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
return nil, err
}
// delete the release
if err = cfgmaps.impl.Delete(key); err != nil {
if err = cfgmaps.impl.Delete(key, &api.DeleteOptions{}); err != nil {
return rls, err
}
return rls, nil

@ -19,9 +19,9 @@ import (
"testing"
"github.com/gogo/protobuf/proto"
"k8s.io/kubernetes/pkg/api"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/kubernetes/pkg/api"
)
func TestConfigMapName(t *testing.T) {

@ -20,10 +20,11 @@ import (
"fmt"
"testing"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/kubernetes/pkg/api"
kberrs "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
func releaseStub(name string, vers int32, code rspb.Status_Code) *rspb.Release {
@ -73,7 +74,7 @@ func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps
// MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface
type MockConfigMapsInterface struct {
unversioned.ConfigMapsInterface
internalversion.ConfigMapInterface
objects map[string]*api.ConfigMap
}
@ -132,7 +133,7 @@ func (mock *MockConfigMapsInterface) Update(cfgmap *api.ConfigMap) (*api.ConfigM
}
// Delete deletes a ConfigMap by name.
func (mock *MockConfigMapsInterface) Delete(name string) error {
func (mock *MockConfigMapsInterface) Delete(name string, opts *api.DeleteOptions) error {
if _, ok := mock.objects[name]; !ok {
return kberrs.NewNotFound(api.Resource("tests"), name)
}

@ -139,6 +139,19 @@ func (s *Storage) History(name string) ([]*rspb.Release, error) {
return l, nil
}
func (s *Storage) Last(name string) (*rspb.Release, error) {
h, err := s.History(name)
if err != nil {
return nil, err
}
if len(h) == 0 {
return nil, fmt.Errorf("no revision for release %q", name)
}
relutil.Reverse(h, relutil.SortByRevision)
return h[0], nil
}
// makeKey concatenates a release name and version into
// a string with format ```<release_name>#v<version>```.
// This key is used to uniquely identify storage objects.

@ -217,6 +217,38 @@ func TestStorageHistory(t *testing.T) {
}
}
func TestStorageLast(t *testing.T) {
storage := Init(driver.NewMemory())
const name = "angry-bird"
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.Status_SUPERSEDED}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.Status_SUPERSEDED}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.Status_SUPERSEDED}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.Status_FAILED}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)")
assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)")
assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)")
}
setup()
h, err := storage.Last(name)
if err != nil {
t.Fatalf("Failed to query for release history (%q): %s\n", name, err)
}
if h.Version != 4 {
t.Errorf("Expected revision 4, got %d", h.Version)
}
}
type ReleaseTestData struct {
Name string
Version int32

@ -31,8 +31,6 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
)
// TillerNamespace is the namespace tiller is running in.
@ -134,9 +132,6 @@ type KubeClient interface {
// reader must contain a YAML stream (one or more YAML documents separated
// by "\n---\n").
Update(namespace string, originalReader, modifiedReader io.Reader) error
// APIClient gets a raw API client for Kubernetes.
APIClient() (unversioned.Interface, error)
}
// PrintingKubeClient implements KubeClient, but simply prints the reader to
@ -145,14 +140,6 @@ type PrintingKubeClient struct {
Out io.Writer
}
// APIClient always returns an error.
//
// The printing client does not have access to a Kubernetes client at all. So it
// will always return an error if the client is accessed.
func (p *PrintingKubeClient) APIClient() (unversioned.Interface, error) {
return testclient.NewSimpleFake(), nil
}
// Create prints the values of what would be created with a real KubeClient.
func (p *PrintingKubeClient) Create(ns string, r io.Reader) error {
_, err := io.Copy(p.Out, r)

@ -23,8 +23,6 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart"
unversionedclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
)
type mockEngine struct {
@ -35,12 +33,7 @@ func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]s
return e.out, nil
}
type mockKubeClient struct {
}
func (k *mockKubeClient) APIClient() (unversionedclient.Interface, error) {
return testclient.NewSimpleFake(), nil
}
type mockKubeClient struct{}
func (k *mockKubeClient) Create(ns string, r io.Reader) error {
return nil

@ -23,6 +23,7 @@ import (
"strings"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/proto/hapi/release"
)

@ -18,6 +18,7 @@ package tiller
import (
"golang.org/x/net/context"
tpb "k8s.io/helm/pkg/proto/hapi/services"
relutil "k8s.io/helm/pkg/releaseutil"
)

@ -21,7 +21,6 @@ import (
"testing"
"k8s.io/helm/pkg/helm"
rpb "k8s.io/helm/pkg/proto/hapi/release"
tpb "k8s.io/helm/pkg/proto/hapi/services"
)

@ -21,15 +21,17 @@ import (
"errors"
"fmt"
"log"
"path/filepath"
"path"
"regexp"
"strings"
"google.golang.org/grpc/metadata"
"github.com/technosophos/moniker"
ctx "golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/kube"
@ -81,15 +83,28 @@ var ListDefaultLimit int64 = 512
// prevents an empty string from matching.
var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$")
// maxMsgSize use 10MB as the default message size limit.
// grpc library default is 4MB
var maxMsgSize = 1024 * 1024 * 10
// NewServer creates a new grpc server.
func NewServer() *grpc.Server {
return grpc.NewServer(
grpc.MaxMsgSize(maxMsgSize),
)
}
// ReleaseServer implements the server-side gRPC endpoint for the HAPI services.
type ReleaseServer struct {
env *environment.Environment
env *environment.Environment
clientset internalclientset.Interface
}
// NewReleaseServer creates a new release server.
func NewReleaseServer(env *environment.Environment) *ReleaseServer {
func NewReleaseServer(env *environment.Environment, clientset internalclientset.Interface) *ReleaseServer {
return &ReleaseServer{
env: env,
env: env,
clientset: clientset,
}
}
@ -229,17 +244,11 @@ func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease
var rel *release.Release
if req.Version <= 0 {
h, err := s.env.Releases.History(req.Name)
var err error
rel, err = s.env.Releases.Last(req.Name)
if err != nil {
return nil, fmt.Errorf("getting deployed release '%s': %s", req.Name, err)
return nil, fmt.Errorf("getting deployed release %q: %s", req.Name, err)
}
if len(h) < 1 {
return nil, errMissingRelease
}
relutil.Reverse(h, relutil.SortByRevision)
rel = h[0]
} else {
var err error
if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil {
@ -376,7 +385,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
}
// finds the non-deleted release with the given name
currentRelease, err := s.env.Releases.Deployed(req.Name)
currentRelease, err := s.env.Releases.Last(req.Name)
if err != nil {
return nil, nil, err
}
@ -504,17 +513,10 @@ func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*
return nil, nil, errInvalidRevision
}
// finds the non-deleted release with the given name
h, err := s.env.Releases.History(req.Name)
crls, err := s.env.Releases.Last(req.Name)
if err != nil {
return nil, nil, err
}
if len(h) <= 1 {
return nil, nil, errors.New("no revision to rollback")
}
relutil.SortByRevision(h)
crls := h[len(h)-1]
rbv := req.Version
if req.Version == 0 {
@ -694,15 +696,10 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return rel, nil
}
func (s *ReleaseServer) getVersionSet() (versionSet, error) {
func getVersionSet(client discovery.ServerGroupsInterface) (versionSet, error) {
defVersions := newVersionSet("v1")
cli, err := s.env.KubeClient.APIClient()
if err != nil {
log.Printf("API Client for Kubernetes is missing: %s.", err)
return defVersions, err
}
groups, err := cli.Discovery().ServerGroups()
groups, err := client.ServerGroups()
if err != nil {
return defVersions, err
}
@ -735,7 +732,8 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
for k, v := range files {
if strings.HasSuffix(k, notesFileSuffix) {
// Only apply the notes if it belongs to the parent chart
if k == filepath.Join(ch.Metadata.Name, "templates", notesFileSuffix) {
// Note: Do not use filePath.Join since it creates a path with \ which is not expected
if k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix) {
notes = v
}
delete(files, k)
@ -745,7 +743,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also
// removed here.
vs, err := s.getVersionSet()
vs, err := getVersionSet(s.clientset.Discovery())
if err != nil {
return nil, nil, "", fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err)
}
@ -948,7 +946,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
}
log.Printf("uninstall: Deleting %s", req.Name)
rel.Info.Status.Code = release.Status_DELETED
rel.Info.Status.Code = release.Status_DELETING
rel.Info.Deleted = timeconv.Now()
res := &services.UninstallReleaseResponse{Release: rel}
@ -958,14 +956,13 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
}
}
vs, err := s.getVersionSet()
vs, err := getVersionSet(s.clientset.Discovery())
if err != nil {
return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err)
}
// From here on out, the release is currently considered to be in Status_DELETED
// state. See https://github.com/kubernetes/helm/issues/1511 for a better way
// to do this.
// From here on out, the release is currently considered to be in Status_DELETING
// state.
if err := s.env.Releases.Update(rel); err != nil {
log.Printf("uninstall: Failed to store updated release: %s", err)
}
@ -980,9 +977,14 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
return nil, fmt.Errorf("corrupted release record. You must manually delete the resources: %s", err)
}
filesToKeep, filesToDelete := filterManifestsToKeep(files)
if len(filesToKeep) > 0 {
res.Info = summarizeKeptManifests(filesToKeep)
}
// Collect the errors, and return them later.
es := []string{}
for _, file := range files {
for _, file := range filesToDelete {
b := bytes.NewBufferString(file.content)
if err := s.env.KubeClient.Delete(rel.Namespace, b); err != nil {
log.Printf("uninstall: Failed deletion of %q: %s", req.Name, err)
@ -1006,6 +1008,11 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
}
}
rel.Info.Status.Code = release.Status_DELETED
if err := s.env.Releases.Update(rel); err != nil {
log.Printf("uninstall: Failed to store updated release: %s", err)
}
var errs error
if len(es) > 0 {
errs = fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; "))

@ -28,6 +28,7 @@ import (
"github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
@ -50,6 +51,16 @@ data:
name: value
`
var manifestWithKeep = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm-keep
annotations:
"helm.sh/resource-policy": keep
data:
name: value
`
var manifestWithUpgradeHooks = `apiVersion: v1
kind: ConfigMap
metadata:
@ -72,7 +83,8 @@ data:
func rsFixture() *ReleaseServer {
return &ReleaseServer{
env: mockEnvironment(),
env: mockEnvironment(),
clientset: fake.NewSimpleClientset(),
}
}
@ -168,7 +180,7 @@ func TestValidName(t *testing.T) {
func TestGetVersionSet(t *testing.T) {
rs := rsFixture()
vs, err := rs.getVersionSet()
vs, err := getVersionSet(rs.clientset.Discovery())
if err != nil {
t.Error(err)
}
@ -1032,6 +1044,63 @@ func TestUninstallPurgeDeleteRelease(t *testing.T) {
}
}
func TestUninstallReleaseWithKeepPolicy(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
name := "angry-bunny"
rs.env.Releases.Create(releaseWithKeepStub(name))
req := &services.UninstallReleaseRequest{
Name: name,
}
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Fatalf("Failed uninstall: %s", err)
}
if res.Release.Name != name {
t.Errorf("Expected angry-bunny, got %q", res.Release.Name)
}
if res.Release.Info.Status.Code != release.Status_DELETED {
t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code)
}
if res.Info == "" {
t.Errorf("Expected response info to not be empty")
} else {
if !strings.Contains(res.Info, "[ConfigMap] test-cm-keep") {
t.Errorf("unexpected output: %s", res.Info)
}
}
}
func releaseWithKeepStub(rlsName string) *release.Release {
ch := &chart.Chart{
Metadata: &chart.Metadata{
Name: "bunnychart",
},
Templates: []*chart.Template{
{Name: "templates/configmap", Data: []byte(manifestWithKeep)},
},
}
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
return &release.Release{
Name: rlsName,
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
},
Chart: ch,
Config: &chart.Config{Raw: `name: value`},
Version: 1,
Manifest: manifestWithKeep,
}
}
func TestUninstallReleaseNoHooks(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()

@ -0,0 +1,65 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tiller
import (
"strings"
)
// resourcePolicyAnno is the annotation name for a resource policy
const resourcePolicyAnno = "helm.sh/resource-policy"
// keepPolicy is the resource policy type for keep
//
// This resource policy type allows resources to skip being deleted
// during an uninstallRelease action.
const keepPolicy = "keep"
func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) {
remaining := []manifest{}
keep := []manifest{}
for _, m := range manifests {
if m.head.Metadata == nil || m.head.Metadata.Annotations == nil || len(m.head.Metadata.Annotations) == 0 {
remaining = append(remaining, m)
continue
}
resourcePolicyType, ok := m.head.Metadata.Annotations[resourcePolicyAnno]
if !ok {
remaining = append(remaining, m)
continue
}
resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType))
if resourcePolicyType == keepPolicy {
keep = append(keep, m)
}
}
return keep, remaining
}
func summarizeKeptManifests(manifests []manifest) string {
message := "These resources were kept due to the resource policy:\n"
for _, m := range manifests {
details := "[" + m.head.Kind + "] " + m.head.Metadata.Name + "\n"
message = message + details
}
return message
}

@ -26,7 +26,7 @@ var (
// Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements.
// Increment patch number for critical fixes to existing releases.
Version = "v2.0.0"
Version = "v2.1.0"
// BuildMetadata is extra build time data
BuildMetadata = ""

Loading…
Cancel
Save