Merge pull request #1 from kubernetes/master

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

2
.gitignore vendored

@ -1,8 +1,10 @@
.DS_Store .DS_Store
.coverage/ .coverage/
.vimrc .vimrc
.vscode/
_dist/ _dist/
_proto/*.pb.go _proto/*.pb.go
bin/ bin/
rootfs/tiller rootfs/tiller
vendor/ 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 # Kubernetes Helm
[![CircleCI](https://circleci.com/gh/kubernetes/helm.svg?style=svg)](https://circleci.com/gh/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 Helm is a tool for managing Kubernetes charts. Charts are packages of
pre-configured Kubernetes resources. 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: 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) - [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.1.0-darwin-amd64.tar.gz)
- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.0.0-linux-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.0.0-linux-386.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`. 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; SUPERSEDED = 3;
// Status_FAILED indicates that the release was not successfully deployed. // Status_FAILED indicates that the release was not successfully deployed.
FAILED = 4; FAILED = 4;
// Status_DELETING indicates that a delete operation is underway.
DELETING = 5;
} }
Code code = 1; Code code = 1;

@ -256,6 +256,8 @@ message UninstallReleaseRequest {
message UninstallReleaseResponse { message UninstallReleaseResponse {
// Release is the release that was marked deleted. // Release is the release that was marked deleted.
hapi.release.Release release = 1; hapi.release.Release release = 1;
// Info is an uninstall message
string info = 2;
} }
// GetVersionRequest requests for version information. // 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 - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
environment: environment:
GOVERSION: "1.7.3" GOVERSION: "1.7.4"
GOPATH: "${HOME}/.go_workspace" GOPATH: "${HOME}/.go_workspace"
WORKDIR: "${GOPATH}/src/k8s.io/helm" WORKDIR: "${GOPATH}/src/k8s.io/helm"
PROJECT_NAME: "kubernetes-helm" PROJECT_NAME: "kubernetes-helm"

@ -18,6 +18,7 @@ package main
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -86,6 +87,10 @@ func (d *deleteCmd) run() error {
helm.DeleteDisableHooks(d.disableHooks), helm.DeleteDisableHooks(d.disableHooks),
helm.DeletePurge(d.purge), 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) return prettyError(err)
} }

@ -44,6 +44,10 @@ const (
// VerifyAlways will always attempt a verification, and will fail if the // VerifyAlways will always attempt a verification, and will fail if the
// verification fails. // verification fails.
VerifyAlways 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. // 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 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 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 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. // For VerifyNever and VerifyIfPossible, the Verification may be empty.
// //
@ -104,6 +109,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, nil, err return destfile, nil, err
} }
if c.Verify != VerifyLater {
ver, err = VerifyChart(destfile, c.Keyring) ver, err = VerifyChart(destfile, c.Keyring)
if err != nil { if err != nil {
// Fail always in this case, since it means the verification step // Fail always in this case, since it means the verification step
@ -111,6 +117,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, ver, err return destfile, ver, err
} }
} }
}
return destfile, ver, nil return destfile, ver, nil
} }

@ -153,3 +153,47 @@ func TestDownloadTo(t *testing.T) {
return 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
}
}

@ -52,6 +52,7 @@ type fetchCmd struct {
version string version string
verify bool verify bool
verifyLater bool
keyring string keyring string
out io.Writer 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.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.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.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.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.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") 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 { if f.verify {
c.Verify = downloader.VerifyAlways 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 // 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 { 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. // After verification, untar the chart into the requested directory.

@ -18,8 +18,10 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"testing" "testing"
"k8s.io/helm/pkg/repo/repotest" "k8s.io/helm/pkg/repo/repotest"
@ -46,6 +48,7 @@ func TestFetchCmd(t *testing.T) {
failExpect string failExpect string
expectFile string expectFile string
expectDir bool expectDir bool
expectVerify bool
}{ }{
{ {
name: "Basic chart fetch", name: "Basic chart fetch",
@ -76,6 +79,7 @@ func TestFetchCmd(t *testing.T) {
chart: "test/signtest", chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"}, flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"},
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
expectVerify: true,
}, },
{ {
name: "Fetch and fail verify", name: "Fetch and fail verify",
@ -87,7 +91,7 @@ func TestFetchCmd(t *testing.T) {
{ {
name: "Fetch and untar", name: "Fetch and untar",
chart: "test/signtest", chart: "test/signtest",
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"}, flags: []string{"--untar", "--untardir", "signtest"},
expectFile: "./signtest", expectFile: "./signtest",
expectDir: true, expectDir: true,
}, },
@ -97,6 +101,7 @@ func TestFetchCmd(t *testing.T) {
flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"}, flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"},
expectFile: "./signtest", expectFile: "./signtest",
expectDir: true, expectDir: true,
expectVerify: true,
}, },
} }
@ -126,6 +131,15 @@ func TestFetchCmd(t *testing.T) {
t.Errorf("%q reported error: %s", tt.name, err) t.Errorf("%q reported error: %s", tt.name, err)
continue 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) ef := filepath.Join(outdir, tt.expectFile)
fi, err := os.Stat(ef) fi, err := os.Stat(ef)

@ -28,18 +28,18 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/tiller/environment"
) )
const ( const (
localRepoIndexFilePath = "index.yaml" localRepoIndexFilePath = "index.yaml"
homeEnvVar = "HELM_HOME" homeEnvVar = "HELM_HOME"
hostEnvVar = "HELM_HOST" hostEnvVar = "HELM_HOST"
tillerNamespace = "kube-system"
) )
var ( var (
@ -145,7 +145,7 @@ func main() {
func setupConnection(c *cobra.Command, args []string) error { func setupConnection(c *cobra.Command, args []string) error {
if tillerHost == "" { if tillerHost == "" {
tunnel, err := newTillerPortForwarder(tillerNamespace, kubeContext) tunnel, err := newTillerPortForwarder(environment.TillerNamespace, kubeContext)
if err != nil { if err != nil {
return err return err
} }
@ -199,12 +199,12 @@ func homePath() string {
// getKubeClient is a convenience method for creating kubernetes config and client // getKubeClient is a convenience method for creating kubernetes config and client
// for a given kubeconfig context // 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() config, err := kube.GetConfig(context).ClientConfig()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("could not get kubernetes config for context '%s': %s", context, err) 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 { if err != nil {
return nil, nil, fmt.Errorf("could not get kubernetes client: %s", err) 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" "github.com/spf13/cobra"
kerrors "k8s.io/kubernetes/pkg/api/errors" 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/helmpath"
"k8s.io/helm/cmd/helm/installer" "k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/tiller/environment"
) )
const initDesc = ` const initDesc = `
@ -65,15 +66,17 @@ type initCmd struct {
image string image string
clientOnly bool clientOnly bool
canary bool canary bool
namespace string
dryRun bool dryRun bool
out io.Writer out io.Writer
home helmpath.Home home helmpath.Home
kubeClient unversioned.DeploymentsNamespacer kubeClient extensionsclient.DeploymentsGetter
} }
func newInitCmd(out io.Writer) *cobra.Command { func newInitCmd(out io.Writer) *cobra.Command {
i := &initCmd{ i := &initCmd{
out: out, out: out,
namespace: environment.TillerNamespace,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -102,7 +105,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
func (i *initCmd) run() error { func (i *initCmd) run() error {
if flagDebug { if flagDebug {
m, err := installer.DeploymentManifest(i.image, i.canary) m, err := installer.DeploymentManifest(i.namespace, i.image, i.canary)
if err != nil { if err != nil {
return err return err
} }
@ -124,7 +127,7 @@ func (i *initCmd) run() error {
} }
i.kubeClient = c 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) { if !kerrors.IsAlreadyExists(err) {
return fmt.Errorf("error installing: %s", err) return fmt.Errorf("error installing: %s", err)
} }

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

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

@ -55,6 +55,12 @@ or
$ helm install --set name=prod ./redis $ 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, 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 the '--debug' and '--dry-run' flags can be combined. This will still require a
round-trip to the Tiller server. round-trip to the Tiller server.
@ -86,7 +92,7 @@ charts in a repository, use 'helm search'.
type installCmd struct { type installCmd struct {
name string name string
namespace string namespace string
valuesFile string valueFiles valueFiles
chartPath string chartPath string
dryRun bool dryRun bool
disableHooks bool disableHooks bool
@ -100,6 +106,23 @@ type installCmd struct {
version string 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 { func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
inst := &installCmd{ inst := &installCmd{
out: out, out: out,
@ -126,7 +149,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() 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.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.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
@ -197,19 +220,54 @@ func (i *installCmd) run() error {
return nil 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) { func (i *installCmd) vals() ([]byte, error) {
base := map[string]interface{}{} base := map[string]interface{}{}
// User specified a values file via -f/--values // User specified a values files via -f/--values
if i.valuesFile != "" { for _, filePath := range i.valueFiles {
bytes, err := ioutil.ReadFile(i.valuesFile) currentMap := map[string]interface{}{}
bytes, err := ioutil.ReadFile(filePath)
if err != nil { if err != nil {
return []byte{}, err return []byte{}, err
} }
if err := yaml.Unmarshal(bytes, &base); err != nil { if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", i.valuesFile, err) 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 { if err := strvals.ParseInto(i.values, base); err != nil {

@ -18,6 +18,7 @@ package main
import ( import (
"io" "io"
"reflect"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
@ -51,6 +52,22 @@ func TestInstall(t *testing.T) {
resp: releaseMock(&releaseOptions{name: "virgil"}), resp: releaseMock(&releaseOptions{name: "virgil"}),
expected: "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 // Install, no charts
{ {
name: "install with no chart specified", 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/api"
"k8s.io/kubernetes/pkg/apis/extensions" "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/kubernetes/pkg/util/intstr"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
@ -37,27 +37,27 @@ const defaultImage = "gcr.io/kubernetes-helm/tiller"
// command failed. // command failed.
// //
// If verbose is true, this will print the manifest to stdout. // If verbose is true, this will print the manifest to stdout.
func Install(client unversioned.DeploymentsNamespacer, namespace, image string, canary, verbose bool) error { func Install(client extensionsclient.DeploymentsGetter, namespace, image string, canary, verbose bool) error {
obj := deployment(image, canary) obj := deployment(namespace, image, canary)
_, err := client.Deployments(namespace).Create(obj) _, err := client.Deployments(obj.Namespace).Create(obj)
return err return err
} }
// deployment gets the deployment object that installs Tiller. // 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 { switch {
case canary: case canary:
image = defaultImage + ":canary" image = defaultImage + ":canary"
case image == "": case image == "":
image = fmt.Sprintf("%s:%s", defaultImage, version.Version) 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 // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource. // resource.
func DeploymentManifest(image string, canary bool) (string, error) { func DeploymentManifest(namespace, image string, canary bool) (string, error) {
obj := deployment(image, canary) obj := deployment(namespace, image, canary)
buf, err := yaml.Marshal(obj) buf, err := yaml.Marshal(obj)
return string(buf), err return string(buf), err
@ -68,10 +68,11 @@ func generateLabels(labels map[string]string) map[string]string {
return labels return labels
} }
func generateDeployment(image string) *extensions.Deployment { func generateDeployment(namespace, image string) *extensions.Deployment {
labels := generateLabels(map[string]string{"name": "tiller"}) labels := generateLabels(map[string]string{"name": "tiller"})
d := &extensions.Deployment{ d := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Namespace: namespace,
Name: "tiller-deploy", Name: "tiller-deploy",
Labels: labels, Labels: labels,
}, },
@ -86,7 +87,7 @@ func generateDeployment(image string) *extensions.Deployment {
{ {
Name: "tiller", Name: "tiller",
Image: image, Image: image,
ImagePullPolicy: "Always", ImagePullPolicy: "IfNotPresent",
Ports: []api.ContainerPort{{ContainerPort: 44134, Name: "tiller"}}, Ports: []api.ContainerPort{{ContainerPort: 44134, Name: "tiller"}},
LivenessProbe: &api.Probe{ LivenessProbe: &api.Probe{
Handler: api.Handler{ Handler: api.Handler{

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

@ -67,6 +67,7 @@ type listCmd struct {
out io.Writer out io.Writer
all bool all bool
deleted bool deleted bool
deleting bool
deployed bool deployed bool
failed bool failed bool
superseded 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.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.all, "all", false, "show all releases, not just the ones marked DEPLOYED")
f.BoolVar(&list.deleted, "deleted", false, "show deleted releases") 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.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") f.BoolVar(&list.failed, "failed", false, "show failed releases")
// TODO: Do we want this as a feature of 'helm list'? // 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_UNKNOWN,
release.Status_DEPLOYED, release.Status_DEPLOYED,
release.Status_DELETED, release.Status_DELETED,
// TODO: Should we return superseded records? These are records release.Status_DELETING,
// that were replaced by an upgrade.
//release.Status_SUPERSEDED,
release.Status_FAILED, release.Status_FAILED,
} }
} }
@ -178,6 +178,9 @@ func (l *listCmd) statusCodes() []release.Status_Code {
if l.deleted { if l.deleted {
status = append(status, release.Status_DELETED) status = append(status, release.Status_DELETED)
} }
if l.deleting {
status = append(status, release.Status_DELETING)
}
if l.failed { if l.failed {
status = append(status, release.Status_FAILED) status = append(status, release.Status_FAILED)
} }
@ -194,7 +197,7 @@ func (l *listCmd) statusCodes() []release.Status_Code {
func formatList(rels []*release.Release) string { func formatList(rels []*release.Release) string {
table := uitable.New() table := uitable.New()
table.MaxColWidth = 30 table.MaxColWidth = 60
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART") table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART")
for _, r := range rels { for _, r := range rels {
c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version) 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. // See note on previous test.
expected: "thomas-guide\natlas-guide", 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 var buf bytes.Buffer

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
@ -121,6 +122,10 @@ func TestLoadPlugins(t *testing.T) {
if pp.Long != tt.long { if pp.Long != tt.long {
t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long) t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long)
} }
// 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 { if err := pp.RunE(pp, tt.args); err != nil {
t.Errorf("Error running %s: %s", tt.use, err) t.Errorf("Error running %s: %s", tt.use, err)
} }
@ -128,6 +133,7 @@ func TestLoadPlugins(t *testing.T) {
t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String()) t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
} }
} }
}
} }
func TestSetupEnv(t *testing.T) { func TestSetupEnv(t *testing.T) {

@ -24,7 +24,7 @@ package search
import ( import (
"errors" "errors"
"path/filepath" "path"
"regexp" "regexp"
"sort" "sort"
"strings" "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 // 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 // 0 slot, so our best bet is to grab the 0 entry and build the index
// entry off of that. // 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 { if !all {
i.lines[fname] = indstr(rname, ref[0]) i.lines[fname] = indstr(rname, ref[0])
i.charts[fname] = 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 }} release: {{.Release.Name | quote }}
# This makes it easy to audit chart usage. # This makes it easy to audit chart usage.
chart: "{{.Chart.Name}}-{{.Chart.Version}}" chart: "{{.Chart.Name}}-{{.Chart.Version}}"
values: {{.Values.test.Name}}
annotations: annotations:
"helm.sh/created": {{.Release.Time.Seconds | quote }} "helm.sh/created": {{.Release.Time.Seconds | quote }}
spec: spec:

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"k8s.io/kubernetes/pkg/api" "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/kubernetes/pkg/labels"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
@ -35,16 +35,16 @@ func newTillerPortForwarder(namespace, context string) (*kube.Tunnel, error) {
return nil, err return nil, err
} }
podName, err := getTillerPodName(client, namespace) podName, err := getTillerPodName(client.Core(), namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
const tillerPort = 44134 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() 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 // TODO use a const for labels
selector := labels.Set{"app": "helm", "name": "tiller"}.AsSelector() selector := labels.Set{"app": "helm", "name": "tiller"}.AsSelector()
pod, err := getFirstRunningPod(client, namespace, selector) pod, err := getFirstRunningPod(client, namespace, selector)
@ -54,7 +54,7 @@ func getTillerPodName(client unversioned.PodsNamespacer, namespace string) (stri
return pod.ObjectMeta.GetName(), nil 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} options := api.ListOptions{LabelSelector: selector}
pods, err := client.Pods(namespace).List(options) pods, err := client.Pods(namespace).List(options)
if err != nil { if err != nil {

@ -20,7 +20,7 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "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 { func mockTillerPod() api.Pod {
@ -74,8 +74,8 @@ func TestGetFirstPod(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
client := testclient.NewSimpleFake(&api.PodList{Items: tt.pods}) client := fake.NewSimpleClientset(&api.PodList{Items: tt.pods})
name, err := getTillerPodName(client, api.NamespaceDefault) name, err := getTillerPodName(client.Core(), api.NamespaceDefault)
if (err != nil) != tt.err { if (err != nil) != tt.err {
t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, 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 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. 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 { type upgradeCmd struct {
@ -49,7 +55,7 @@ type upgradeCmd struct {
client helm.Interface client helm.Interface
dryRun bool dryRun bool
disableHooks bool disableHooks bool
valuesFile string valueFiles valueFiles
values string values string
verify bool verify bool
keyring string keyring string
@ -84,7 +90,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() 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.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.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") 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, client: u.client,
out: u.out, out: u.out,
name: u.release, name: u.release,
valuesFile: u.valuesFile, valueFiles: u.valueFiles,
dryRun: u.dryRun, dryRun: u.dryRun,
verify: u.verify, verify: u.verify,
disableHooks: u.disableHooks, disableHooks: u.disableHooks,
@ -164,16 +170,19 @@ func (u *upgradeCmd) run() error {
func (u *upgradeCmd) vals() ([]byte, error) { func (u *upgradeCmd) vals() ([]byte, error) {
base := map[string]interface{}{} base := map[string]interface{}{}
// User specified a values file via -f/--values // User specified a values files via -f/--values
if u.valuesFile != "" { for _, filePath := range u.valueFiles {
bytes, err := ioutil.ReadFile(u.valuesFile) currentMap := map[string]interface{}{}
bytes, err := ioutil.ReadFile(filePath)
if err != nil { if err != nil {
return []byte{}, err return []byte{}, err
} }
if err := yaml.Unmarshal(bytes, base); err != nil { if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", u.valuesFile, err) 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 { if err := strvals.ParseInto(u.values, base); err != nil {

@ -17,10 +17,22 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"runtime"
"testing" "testing"
) )
func TestVerifyCmd(t *testing.T) { 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 { tests := []struct {
name string name string
args []string args []string
@ -36,7 +48,7 @@ func TestVerifyCmd(t *testing.T) {
{ {
name: "verify requires that chart exists", name: "verify requires that chart exists",
args: []string{"no/such/file"}, 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, err: true,
}, },
{ {
@ -48,7 +60,7 @@ func TestVerifyCmd(t *testing.T) {
{ {
name: "verify requires that chart has prov file", name: "verify requires that chart has prov file",
args: []string{"testdata/testcharts/compressedchart-0.1.0.tgz"}, 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, err: true,
}, },
{ {

@ -24,8 +24,8 @@ import (
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"google.golang.org/grpc"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
@ -41,7 +41,7 @@ const (
// rootServer is the root gRPC server. // rootServer is the root gRPC server.
// //
// Each gRPC service registers itself to this server during init(). // Each gRPC service registers itself to this server during init().
var rootServer = grpc.NewServer() var rootServer = tiller.NewServer()
// env is the default environment. // env is the default environment.
// //
@ -81,15 +81,16 @@ func main() {
} }
func start(c *cobra.Command, args []string) { 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 { switch store {
case storageMemory: case storageMemory:
env.Releases = storage.Init(driver.NewMemory()) env.Releases = storage.Init(driver.NewMemory())
case storageConfigMap: case storageConfigMap:
c, err := env.KubeClient.APIClient() env.Releases = storage.Init(driver.NewConfigMaps(clientset.Core().ConfigMaps(environment.TillerNamespace)))
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot initialize Kubernetes connection: %s", err)
}
env.Releases = storage.Init(driver.NewConfigMaps(c.ConfigMaps(environment.TillerNamespace)))
} }
lstn, err := net.Listen("tcp", grpcAddr) lstn, err := net.Listen("tcp", grpcAddr)
@ -109,7 +110,7 @@ func start(c *cobra.Command, args []string) {
srvErrCh := make(chan error) srvErrCh := make(chan error)
probeErrCh := make(chan error) probeErrCh := make(chan error)
go func() { go func() {
svc := tiller.NewReleaseServer(env) svc := tiller.NewReleaseServer(env, clientset)
services.RegisterReleaseServiceServer(rootServer, svc) services.RegisterReleaseServiceServer(rootServer, svc)
if err := rootServer.Serve(lstn); err != nil { if err := rootServer.Serve(lstn); err != nil {
srvErrCh <- err 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. - 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. - 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 ## 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. 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 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 ## Glob patterns
As your chart grows, you may find you have a greater need to organize your 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 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: For example, imagine the directory structure:
@ -101,6 +132,36 @@ Or
{{ end }} {{ 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 ## Secrets
When working with a Secret resource, you can import a file and have the template base-64 encode it for you: 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 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`. 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. 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. 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. 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"}} {{define "fullname"}}
{{- $name := default "nginx" .Values.nameOverride -}} {{- $name := default "nginx" .Values.nameOverride -}}
{{printf "%s-%s" .Release.Name $name | trunc 24 -}} {{printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{end}} {{end}}

@ -106,6 +106,34 @@ follows:
See this issue for more information: https://github.com/kubernetes/helm/issues/1455 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 ## Upgrading
My Helm used to work, then I upgrade. Now it is broken. My Helm used to work, then I upgrade. Now it is broken.

265
glide.lock generated

@ -1,5 +1,5 @@
hash: 8ae84a3225f6cc31f91cc42dc8cf816792a989838254964cdcaaa57c75c37cdc hash: 2fc61aa64319b4dc6cd4a107c46a25264865f2e07362d7dcd8a3db002278d6a6
updated: 2016-12-01T17:35:05.940550036-07:00 updated: 2016-12-13T17:07:47.402783003-05:00
imports: imports:
- name: cloud.google.com/go - name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821 version: 3b1ae45394a234c385be014e9a488f2bb6eef821
@ -17,22 +17,13 @@ imports:
- name: github.com/blang/semver - name: github.com/blang/semver
version: 31b736133b98f26d5e078ec9eb591666edfd091f version: 31b736133b98f26d5e078ec9eb591666edfd091f
- name: github.com/coreos/go-oidc - name: github.com/coreos/go-oidc
version: 5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b version: 5644a2f50e2d2d5ba0b474bc5bc55fea1925936d
subpackages: subpackages:
- http - http
- jose - jose
- key - key
- oauth2 - oauth2
- oidc - oidc
- name: github.com/coreos/go-systemd
version: 4484981625c1a6a2ecb40a390fcb6a9bcfee76e3
subpackages:
- activation
- daemon
- dbus
- journal
- unit
- util
- name: github.com/coreos/pkg - name: github.com/coreos/pkg
version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8 version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8
subpackages: subpackages:
@ -49,6 +40,8 @@ imports:
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages: subpackages:
- spew - spew
- name: github.com/dgrijalva/jwt-go
version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: cd27f179f2c10c5d300e6d09025b538c475b0d51 version: cd27f179f2c10c5d300e6d09025b538c475b0d51
subpackages: subpackages:
@ -101,10 +94,20 @@ imports:
- swagger - swagger
- name: github.com/evanphx/json-patch - name: github.com/evanphx/json-patch
version: 465937c80b3c07a7c7ad20cc934898646a91c1de version: 465937c80b3c07a7c7ad20cc934898646a91c1de
- name: github.com/exponent-io/jsonpath
version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5
- name: github.com/ghodss/yaml - name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee 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 - name: github.com/gobwas/glob
version: 0354991b92587e2742549d3036f3b5bae5ab03f2 version: bea32b9cd2d6f55753d94a28e959b13f0244797a
subpackages: subpackages:
- compiler - compiler
- match - match
@ -153,50 +156,6 @@ imports:
- proto - proto
- ptypes/any - ptypes/any
- ptypes/timestamp - 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 - name: github.com/google/gofuzz
version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5 version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5
- name: github.com/gosuri/uitable - name: github.com/gosuri/uitable
@ -204,6 +163,8 @@ imports:
subpackages: subpackages:
- util/strutil - util/strutil
- util/wordwrap - util/wordwrap
- name: github.com/howeyc/gopass
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
- name: github.com/imdario/mergo - name: github.com/imdario/mergo
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- name: github.com/inconshreveable/mousetrap - name: github.com/inconshreveable/mousetrap
@ -212,14 +173,26 @@ imports:
version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982 version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982
- name: github.com/juju/ratelimit - name: github.com/juju/ratelimit
version: 77ed1c8a01217656d2080ad51981f6e99adaa177 version: 77ed1c8a01217656d2080ad51981f6e99adaa177
- name: github.com/mailru/easyjson
version: d5b7844b561a7bc640052f1b935f7b800330d7e0
subpackages:
- buffer
- jlexer
- jwriter
- name: github.com/Masterminds/semver - name: github.com/Masterminds/semver
version: 52edfc04e184ecf0962489d167b511b27aeebd61 version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/Masterminds/sprig - name: github.com/Masterminds/sprig
version: 1e60e4ce482a1e2c7b9c9be667535ef152e04300 version: 1e60e4ce482a1e2c7b9c9be667535ef152e04300
- name: github.com/mattn/go-runewidth - name: github.com/mattn/go-runewidth
version: d6bea18f789704b5f83375793155289da36a3c7f version: d6bea18f789704b5f83375793155289da36a3c7f
- name: github.com/mitchellh/go-wordwrap
version: ad45545899c7b13c020ea92b2072220eefad42b8
- name: github.com/pborman/uuid - name: github.com/pborman/uuid
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
- name: github.com/PuerkitoBio/purell
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/russross/blackfriday - name: github.com/russross/blackfriday
version: 300106c228d52c8941d4b3de6054a6062a86dda3 version: 300106c228d52c8941d4b3de6054a6062a86dda3
- name: github.com/satori/go.uuid - name: github.com/satori/go.uuid
@ -229,11 +202,11 @@ imports:
- name: github.com/Sirupsen/logrus - name: github.com/Sirupsen/logrus
version: 51fe59aca108dc5680109e7b2051cbdcfa5a253c version: 51fe59aca108dc5680109e7b2051cbdcfa5a253c
- name: github.com/spf13/cobra - name: github.com/spf13/cobra
version: 6a8bd97bdb1fc0d08a83459940498ea49d3e8c93 version: f62e98d28ab7ad31d707ba837a966378465c7b57
subpackages: subpackages:
- doc - doc
- name: github.com/spf13/pflag - name: github.com/spf13/pflag
version: 367864438f1b1a3c7db4da06a2f55b144e6784e0 version: 5ccb023bc27df288a957c5e994cd44fd19619465
- name: github.com/technosophos/moniker - name: github.com/technosophos/moniker
version: 9f956786b91d9786ca11aa5be6104542fa911546 version: 9f956786b91d9786ca11aa5be6104542fa911546
- name: github.com/ugorji/go - name: github.com/ugorji/go
@ -254,13 +227,15 @@ imports:
- openpgp/s2k - openpgp/s2k
- ssh/terminal - ssh/terminal
- name: golang.org/x/net - name: golang.org/x/net
version: fb93926129b8ec0056f2f458b1f519654814edf0 version: e90d6d0afc4c315a0d87a568ae68577cc15149a0
subpackages: subpackages:
- context - context
- context/ctxhttp - context/ctxhttp
- http2 - http2
- http2/hpack - http2/hpack
- idna
- internal/timeseries - internal/timeseries
- lex/httplex
- trace - trace
- websocket - websocket
- name: golang.org/x/oauth2 - name: golang.org/x/oauth2
@ -270,6 +245,23 @@ imports:
- internal - internal
- jws - jws
- jwt - 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 - name: google.golang.org/appengine
version: 4f7eeb5305a4ba1966344836ba4af9996b7b4e05 version: 4f7eeb5305a4ba1966344836ba4af9996b7b4e05
subpackages: subpackages:
@ -297,88 +289,24 @@ imports:
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/yaml.v2 - name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4 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 - name: k8s.io/kubernetes
version: fd8fac83034df346529c6e11aabceea2db48d663 version: d47846323632bf59c729460fc7344d2df347bf46
subpackages: subpackages:
- cmd/kubeadm/app/apis/kubeadm
- cmd/kubeadm/app/apis/kubeadm/install
- cmd/kubeadm/app/apis/kubeadm/v1alpha1
- federation/apis/federation - federation/apis/federation
- federation/apis/federation/install - federation/apis/federation/install
- federation/apis/federation/v1beta1 - federation/apis/federation/v1beta1
- federation/client/clientset_generated/federation_internalclientset - federation/client/clientset_generated/federation_internalclientset
- federation/client/clientset_generated/federation_internalclientset/typed/core/unversioned - federation/client/clientset_generated/federation_internalclientset/typed/core/internalversion
- federation/client/clientset_generated/federation_internalclientset/typed/extensions/unversioned - federation/client/clientset_generated/federation_internalclientset/typed/extensions/internalversion
- federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned - federation/client/clientset_generated/federation_internalclientset/typed/federation/internalversion
- pkg/api - pkg/api
- pkg/api/annotations - pkg/api/annotations
- pkg/api/endpoints - pkg/api/endpoints
- pkg/api/errors - pkg/api/errors
- pkg/api/events
- pkg/api/install - pkg/api/install
- pkg/api/meta - pkg/api/meta
- pkg/api/meta/metatypes - pkg/api/meta/metatypes
@ -392,11 +320,13 @@ imports:
- pkg/api/util - pkg/api/util
- pkg/api/v1 - pkg/api/v1
- pkg/api/validation - pkg/api/validation
- pkg/api/validation/path
- pkg/apimachinery - pkg/apimachinery
- pkg/apimachinery/announced
- pkg/apimachinery/registered - pkg/apimachinery/registered
- pkg/apis/apps - pkg/apis/apps
- pkg/apis/apps/install - pkg/apis/apps/install
- pkg/apis/apps/v1alpha1 - pkg/apis/apps/v1beta1
- pkg/apis/authentication - pkg/apis/authentication
- pkg/apis/authentication/install - pkg/apis/authentication/install
- pkg/apis/authentication/v1beta1 - pkg/apis/authentication/v1beta1
@ -425,43 +355,60 @@ imports:
- pkg/apis/imagepolicy/v1alpha1 - pkg/apis/imagepolicy/v1alpha1
- pkg/apis/policy - pkg/apis/policy
- pkg/apis/policy/install - pkg/apis/policy/install
- pkg/apis/policy/v1alpha1 - pkg/apis/policy/v1beta1
- pkg/apis/rbac - pkg/apis/rbac
- pkg/apis/rbac/install - pkg/apis/rbac/install
- pkg/apis/rbac/v1alpha1 - pkg/apis/rbac/v1alpha1
- pkg/apis/storage - pkg/apis/storage
- pkg/apis/storage/install - pkg/apis/storage/install
- pkg/apis/storage/util
- pkg/apis/storage/v1beta1 - pkg/apis/storage/v1beta1
- pkg/auth/authenticator
- pkg/auth/user - pkg/auth/user
- pkg/capabilities - pkg/capabilities
- pkg/client/cache - pkg/client/cache
- pkg/client/clientset_generated/internalclientset - pkg/client/clientset_generated/internalclientset
- pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned - pkg/client/clientset_generated/internalclientset/fake
- pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned - pkg/client/clientset_generated/internalclientset/typed/apps/internalversion
- pkg/client/clientset_generated/internalclientset/typed/autoscaling/unversioned - pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/batch/unversioned - pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion
- pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned - pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/core/unversioned - pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion
- pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned - pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/rbac/unversioned - pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion
- pkg/client/clientset_generated/internalclientset/typed/storage/unversioned - 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/metrics
- pkg/client/record - pkg/client/record
- pkg/client/restclient - pkg/client/restclient
- pkg/client/restclient/fake
- pkg/client/retry
- pkg/client/testing/core
- pkg/client/transport - pkg/client/transport
- pkg/client/typed/discovery - pkg/client/typed/discovery
- pkg/client/typed/discovery/fake
- pkg/client/typed/dynamic - pkg/client/typed/dynamic
- pkg/client/unversioned - pkg/client/unversioned
- pkg/client/unversioned/adapters/internalclientset
- pkg/client/unversioned/auth - pkg/client/unversioned/auth
- pkg/client/unversioned/clientcmd - pkg/client/unversioned/clientcmd
- pkg/client/unversioned/clientcmd/api - pkg/client/unversioned/clientcmd/api
- pkg/client/unversioned/clientcmd/api/latest - pkg/client/unversioned/clientcmd/api/latest
- pkg/client/unversioned/clientcmd/api/v1 - pkg/client/unversioned/clientcmd/api/v1
- pkg/client/unversioned/fake
- pkg/client/unversioned/portforward - pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand - pkg/client/unversioned/remotecommand
- pkg/client/unversioned/testclient
- pkg/controller - pkg/controller
- pkg/controller/deployment/util - pkg/controller/deployment/util
- pkg/conversion - pkg/conversion
@ -469,8 +416,10 @@ imports:
- pkg/credentialprovider - pkg/credentialprovider
- pkg/fieldpath - pkg/fieldpath
- pkg/fields - pkg/fields
- pkg/genericapiserver/openapi/common
- pkg/httplog - pkg/httplog
- pkg/kubectl - pkg/kubectl
- pkg/kubectl/cmd/testing
- pkg/kubectl/cmd/util - pkg/kubectl/cmd/util
- pkg/kubectl/resource - pkg/kubectl/resource
- pkg/kubelet/qos - pkg/kubelet/qos
@ -479,7 +428,7 @@ imports:
- pkg/kubelet/types - pkg/kubelet/types
- pkg/labels - pkg/labels
- pkg/master/ports - pkg/master/ports
- pkg/registry/thirdpartyresourcedata - pkg/registry/extensions/thirdpartyresourcedata
- pkg/runtime - pkg/runtime
- pkg/runtime/serializer - pkg/runtime/serializer
- pkg/runtime/serializer/json - pkg/runtime/serializer/json
@ -488,15 +437,17 @@ imports:
- pkg/runtime/serializer/streaming - pkg/runtime/serializer/streaming
- pkg/runtime/serializer/versioning - pkg/runtime/serializer/versioning
- pkg/security/apparmor - pkg/security/apparmor
- pkg/security/podsecuritypolicy/seccomp
- pkg/security/podsecuritypolicy/util - pkg/security/podsecuritypolicy/util
- pkg/selection - pkg/selection
- pkg/serviceaccount
- pkg/storage - pkg/storage
- pkg/types - pkg/types
- pkg/util - pkg/util
- pkg/util/certificates - pkg/util/cert
- pkg/util/clock - pkg/util/clock
- pkg/util/config - pkg/util/config
- pkg/util/crypto - pkg/util/diff
- pkg/util/errors - pkg/util/errors
- pkg/util/exec - pkg/util/exec
- pkg/util/flag - pkg/util/flag
@ -514,10 +465,10 @@ imports:
- pkg/util/labels - pkg/util/labels
- pkg/util/net - pkg/util/net
- pkg/util/net/sets - pkg/util/net/sets
- pkg/util/node
- pkg/util/parsers - pkg/util/parsers
- pkg/util/pod - pkg/util/pod
- pkg/util/rand - pkg/util/rand
- pkg/util/replicaset
- pkg/util/runtime - pkg/util/runtime
- pkg/util/sets - pkg/util/sets
- pkg/util/slice - pkg/util/slice
@ -539,4 +490,14 @@ imports:
- third_party/forked/golang/netutil - third_party/forked/golang/netutil
- third_party/forked/golang/reflect - third_party/forked/golang/reflect
- third_party/forked/golang/template - 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 package: k8s.io/helm
import: import:
- package: golang.org/x/net - package: golang.org/x/net
version: fb93926129b8ec0056f2f458b1f519654814edf0 version: e90d6d0afc4c315a0d87a568ae68577cc15149a0
subpackages: subpackages:
- context - context
- package: github.com/spf13/cobra - package: github.com/spf13/cobra
version: f62e98d28ab7ad31d707ba837a966378465c7b57
- package: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465
- package: github.com/Masterminds/sprig - package: github.com/Masterminds/sprig
version: ^2.7 version: ^2.7
- package: github.com/ghodss/yaml - package: github.com/ghodss/yaml
- package: github.com/Masterminds/semver - package: github.com/Masterminds/semver
version: ~1.2.1 version: ~1.2.2
- package: github.com/technosophos/moniker - package: github.com/technosophos/moniker
- package: github.com/golang/protobuf - package: github.com/golang/protobuf
version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e
@ -20,7 +23,7 @@ import:
- package: google.golang.org/grpc - package: google.golang.org/grpc
version: 1.0.3 version: 1.0.3
- package: k8s.io/kubernetes - package: k8s.io/kubernetes
version: ~1.4.1 version: ~1.5.0
subpackages: subpackages:
- pkg/api - pkg/api
- pkg/api/errors - pkg/api/errors
@ -28,12 +31,14 @@ import:
- pkg/apimachinery/registered - pkg/apimachinery/registered
- pkg/apis/batch - pkg/apis/batch
- pkg/apis/extensions - 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/restclient
- pkg/client/unversioned - pkg/client/typed/discovery
- pkg/client/unversioned/clientcmd - pkg/client/unversioned/clientcmd
- pkg/client/unversioned/portforward - pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand - pkg/client/unversioned/remotecommand
- pkg/client/unversioned/testclient
- pkg/kubectl - pkg/kubectl
- pkg/kubectl/cmd/util - pkg/kubectl/cmd/util
- pkg/kubectl/resource - pkg/kubectl/resource
@ -41,7 +46,6 @@ import:
- pkg/runtime - pkg/runtime
- pkg/util/intstr - pkg/util/intstr
- pkg/util/strategicpatch - pkg/util/strategicpatch
- pkg/util/yaml
- pkg/watch - pkg/watch
- package: github.com/gosuri/uitable - package: github.com/gosuri/uitable
- package: github.com/asaskevich/govalidator - package: github.com/asaskevich/govalidator
@ -51,3 +55,8 @@ import:
- openpgp - openpgp
- package: github.com/gobwas/glob - package: github.com/gobwas/glob
version: ^0.2.1 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. Expand the name of the chart.
*/}} */}}
{{- define "name" -}} {{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 24 -}} {{- default .Chart.Name .Values.nameOverride | trunc 24 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
{{/* {{/*
@ -171,7 +171,7 @@ We truncate at 24 chars because some Kubernetes name fields are limited to this
*/}} */}}
{{- define "fullname" -}} {{- define "fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}} {{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 24 -}} {{- printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
` `

@ -16,6 +16,12 @@ limitations under the License.
package chartutil package chartutil
import ( import (
"encoding/base64"
"path"
"strings"
yaml "gopkg.in/yaml.v2"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/any"
) )
@ -83,3 +89,88 @@ func (f Files) Glob(pattern string) Files {
return nf 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" "testing"
"github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/any"
"github.com/stretchr/testify/assert"
) )
var cases = []struct { var cases = []struct {
@ -28,6 +29,7 @@ var cases = []struct {
{"ship/stowaway.txt", "Legatt"}, {"ship/stowaway.txt", "Legatt"},
{"story/name.txt", "The Secret Sharer"}, {"story/name.txt", "The Secret Sharer"},
{"story/author.txt", "Joseph Conrad"}, {"story/author.txt", "Joseph Conrad"},
{"multiline/test.txt", "bar\nfoo"},
} }
func getTestFiles() []*any.Any { func getTestFiles() []*any.Any {
@ -55,16 +57,56 @@ func TestNewFiles(t *testing.T) {
} }
func TestFileGlob(t *testing.T) { func TestFileGlob(t *testing.T) {
as := assert.New(t)
f := NewFiles(getTestFiles()) f := NewFiles(getTestFiles())
matched := f.Glob("story/**") matched := f.Glob("story/**")
if len(matched) != 2 { as.Len(matched, 2, "Should be two files in glob story/**")
t.Errorf("Expected two files in glob story/**, got %d", len(matched)) 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 got := ToYaml(v); got != expect {
if m != expect { t.Errorf("Expected %q, got %q", expect, got)
t.Errorf("Wrong globbed file content. Expected %s, got %s", expect, m)
} }
} }

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

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

@ -22,7 +22,6 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"reflect"
"strings" "strings"
"time" "time"
@ -30,14 +29,12 @@ import (
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/strategicpatch" "k8s.io/kubernetes/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/watch" "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. // Client represents a client capable of communicating with the Kubernetes API.
type Client struct { type Client struct {
*cmdutil.Factory 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
// Validate idicates whether to load a schema for validation. // Validate idicates whether to load a schema for validation.
Validate bool Validate bool
// SchemaCacheDir is the path for loading cached schema. // SchemaCacheDir is the path for loading cached schema.
@ -64,7 +54,6 @@ type Client struct {
func New(config clientcmd.ClientConfig) *Client { func New(config clientcmd.ClientConfig) *Client {
return &Client{ return &Client{
Factory: cmdutil.NewFactory(config), Factory: cmdutil.NewFactory(config),
IncludeThirdPartyAPIs: true,
Validate: true, Validate: true,
SchemaCacheDir: clientcmd.RecommendedSchemaFile, 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) 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 // Create creates kubernetes resources from an io.reader
// //
// Namespace will set the namespace // Namespace will set the namespace
func (c *Client) Create(namespace string, reader io.Reader) error { 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 err
} }
return perform(c, namespace, reader, createResource) return perform(c, namespace, reader, createResource)
@ -107,7 +90,7 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Builde
if err != nil { if err != nil {
log.Printf("warning: failed to load schema: %s", err) log.Printf("warning: failed to load schema: %s", err)
} }
return c.NewBuilder(c.IncludeThirdPartyAPIs). return c.NewBuilder().
ContinueOnError(). ContinueOnError().
Schema(schema). Schema(schema).
NamespaceParam(namespace). NamespaceParam(namespace).
@ -313,33 +296,22 @@ func deleteResource(info *resource.Info) error {
} }
func updateResource(target *resource.Info, currentObj runtime.Object) error { func updateResource(target *resource.Info, currentObj runtime.Object) error {
encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...) encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
originalSerialization, err := runtime.Encode(encoder, currentObj) original, 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)
if err != nil { if err != nil {
return err return err
} }
editedJS, err := yaml.ToJSON(editedSerialization) modified, err := runtime.Encode(encoder, target.Object)
if err != nil { if err != nil {
return err return err
} }
if reflect.DeepEqual(originalJS, editedJS) { if api.Semantic.DeepEqual(original, modified) {
return ErrAlreadyExists{target.Name} return ErrAlreadyExists{target.Name}
} }
patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, currentObj) patch, err := strategicpatch.CreateTwoWayMergePatch(original, modified, currentObj)
if err != nil { if err != nil {
return err return err
} }
@ -413,21 +385,6 @@ func waitForJob(e watch.Event, name string) (bool, error) {
return false, nil 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) { func deleteUnwantedResources(currentInfos, targetInfos []*resource.Info) {
for _, cInfo := range currentInfos { for _, cInfo := range currentInfos {
if _, ok := findMatchingInfo(cInfo, targetInfos); !ok { if _, ok := findMatchingInfo(cInfo, targetInfos); !ok {

@ -18,51 +18,122 @@ package kube
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
api "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation" "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/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime" "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 { func newPod(name string) api.Pod {
name string return api.Pod{
namespace string ObjectMeta: api.ObjectMeta{Name: name},
modified *resource.Info Spec: api.PodSpec{
currentObj runtime.Object Containers: []api.Container{{
err bool Name: "app:v4",
errMessage string Image: "abc/app:v4",
}{ Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}},
{ }},
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",
}, },
//{
//name: "valid update input",
//modified: createFakeInfo("nginx", map[string]string{"app": "nginx"}),
//currentObj: createFakePod("nginx", nil),
//},
} }
}
for _, tt := range tests { func newPodList(names ...string) api.PodList {
err := updateResource(tt.modified, tt.currentObj) var list api.PodList
if err != nil && err.Error() != tt.errMessage { for _, name := range names {
t.Errorf("%q. expected error message: %v, got %v", tt.name, tt.errMessage, err) list.Items = append(list.Items, newPod(name))
}
return list
}
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 return nil
} }
c := New(nil) f, tf, _, _ := cmdtesting.NewAPIFactory()
c.IncludeThirdPartyAPIs = false c := &Client{Factory: f}
c.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if tt.swaggerFile != "" {
return &fake.RESTClient{}, nil
}
c.Validator = func(validate bool, cacheDir string) (validation.Schema, error) {
if tt.swaggerFile == "" {
return validation.NullSchema{}, nil
}
data, err := ioutil.ReadFile(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 { if err != nil {
t.Fatalf("could not load swagger spec: %s", err) 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) err := perform(c, tt.namespace, tt.reader, fn)
@ -143,14 +212,12 @@ func TestPerform(t *testing.T) {
func TestReal(t *testing.T) { func TestReal(t *testing.T) {
t.Skip("This is a live test, comment this line to run") t.Skip("This is a live test, comment this line to run")
c := New(nil) c := New(nil)
c.IncludeThirdPartyAPIs = false
if err := c.Create("test", strings.NewReader(guestbookManifest)); err != nil { if err := c.Create("test", strings.NewReader(guestbookManifest)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest
c = New(nil) c = New(nil)
c.IncludeThirdPartyAPIs = false
if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil { if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -322,52 +389,3 @@ spec:
ports: ports:
- containerPort: 80 - 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. Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at 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 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, 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. limitations under the License.
*/ */
package helmpath package kube // import "k8s.io/helm/pkg/kube"
import ( import (
"testing" "testing"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
) )
func TestHelmHome(t *testing.T) { func TestEnsureNamespace(t *testing.T) {
hh := Home("/r") client := fake.NewSimpleClientset()
isEq := func(t *testing.T, a, b string) { if err := ensureNamespace(client, "foo"); err != nil {
if a != b { t.Fatalf("unexpected error: %s", err)
t.Errorf("Expected %q, got %q", a, b)
} }
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{} stopChan chan struct{}
readyChan chan struct{} readyChan chan struct{}
config *restclient.Config config *restclient.Config
client *restclient.RESTClient client restclient.Interface
} }
// NewTunnel creates a new tunnel // 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{ return &Tunnel{
config: config, config: config,
client: client, client: client,

@ -2,7 +2,7 @@
{{/* {{/*
Expand the name of the chart. 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. 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"}} {{define "fullname"}}
{{- $name := default "nginx" .Values.nameOverride -}} {{- $name := default "nginx" .Values.nameOverride -}}
{{printf "%s-%s" .Release.Name $name | trunc 24 -}} {{printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{end}} {{end}}

@ -27,6 +27,8 @@ const (
Status_SUPERSEDED Status_Code = 3 Status_SUPERSEDED Status_Code = 3
// Status_FAILED indicates that the release was not successfully deployed. // Status_FAILED indicates that the release was not successfully deployed.
Status_FAILED Status_Code = 4 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{ var Status_Code_name = map[int32]string{
@ -35,6 +37,7 @@ var Status_Code_name = map[int32]string{
2: "DELETED", 2: "DELETED",
3: "SUPERSEDED", 3: "SUPERSEDED",
4: "FAILED", 4: "FAILED",
5: "DELETING",
} }
var Status_Code_value = map[string]int32{ var Status_Code_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
@ -42,6 +45,7 @@ var Status_Code_value = map[string]int32{
"DELETED": 2, "DELETED": 2,
"SUPERSEDED": 3, "SUPERSEDED": 3,
"FAILED": 4, "FAILED": 4,
"DELETING": 5,
} }
func (x Status_Code) String() string { func (x Status_Code) String() string {
@ -79,22 +83,22 @@ func init() {
func init() { proto.RegisterFile("hapi/release/status.proto", fileDescriptor3) } func init() { proto.RegisterFile("hapi/release/status.proto", fileDescriptor3) }
var fileDescriptor3 = []byte{ var fileDescriptor3 = []byte{
// 261 bytes of a gzipped FileDescriptorProto // 269 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0xc1, 0x4e, 0x83, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6f, 0x82, 0x40,
0x10, 0x86, 0xdd, 0x16, 0x41, 0xa6, 0x4d, 0x43, 0x36, 0x3d, 0x80, 0xf1, 0x40, 0x7a, 0xe2, 0xe2, 0x10, 0x86, 0xbb, 0x8a, 0x50, 0x46, 0x63, 0x36, 0x1b, 0x0f, 0xd0, 0xf4, 0x40, 0x3c, 0x71, 0xe9,
0x92, 0xd4, 0x27, 0xa8, 0xee, 0x9a, 0xa8, 0x84, 0x36, 0x60, 0x63, 0xf4, 0x46, 0xcb, 0x58, 0x9b, 0x92, 0xd8, 0x5f, 0x60, 0xbb, 0xdb, 0xc6, 0x94, 0xa0, 0x01, 0x4d, 0x3f, 0x6e, 0x28, 0x53, 0x6b,
0x10, 0xb6, 0x61, 0x97, 0x43, 0x9f, 0xd8, 0xd7, 0x30, 0x2c, 0x6d, 0xec, 0x71, 0xe6, 0xfb, 0x66, 0x42, 0x58, 0xc3, 0xc2, 0xc1, 0x1f, 0xde, 0x7b, 0x03, 0x68, 0xea, 0x71, 0xf7, 0x79, 0xde, 0x79,
0xfe, 0x19, 0x08, 0x7e, 0x8a, 0xc3, 0x3e, 0x6e, 0xb0, 0xc2, 0x42, 0x61, 0xac, 0x74, 0xa1, 0x5b, 0x67, 0xc0, 0xfd, 0x49, 0x8f, 0x87, 0xa0, 0xc4, 0x1c, 0x53, 0x8d, 0x81, 0xae, 0xd2, 0xaa, 0xd6,
0xc5, 0x0e, 0x8d, 0xd4, 0x92, 0x8e, 0x3b, 0xc4, 0x4e, 0xe8, 0x36, 0xd8, 0x49, 0xb9, 0xab, 0x30, 0xfc, 0x58, 0xaa, 0x4a, 0xb1, 0x51, 0x83, 0xf8, 0x19, 0xdd, 0xb9, 0x7b, 0xa5, 0xf6, 0x39, 0x06,
0x36, 0x6c, 0xd3, 0x7e, 0xc7, 0x45, 0x7d, 0xec, 0xc5, 0xd9, 0x2f, 0x01, 0x3b, 0x37, 0x93, 0xf4, 0x2d, 0xdb, 0xd6, 0xdf, 0x41, 0x5a, 0x9c, 0x3a, 0x71, 0xfa, 0x4b, 0xc0, 0x4c, 0xda, 0x24, 0x7b,
0x1e, 0xac, 0xad, 0x2c, 0xd1, 0x27, 0x21, 0x89, 0x26, 0xf3, 0x80, 0x5d, 0xae, 0x60, 0xbd, 0xc3, 0x00, 0x63, 0xa7, 0x32, 0x74, 0x88, 0x47, 0xfc, 0xf1, 0xcc, 0xe5, 0xd7, 0x23, 0x78, 0xe7, 0xf0,
0x9e, 0x64, 0x89, 0x99, 0xd1, 0x28, 0x03, 0xa7, 0x44, 0x5d, 0xec, 0x2b, 0xe5, 0x0f, 0x42, 0x12, 0x67, 0x95, 0x61, 0xdc, 0x6a, 0x8c, 0x83, 0x95, 0x61, 0x95, 0x1e, 0x72, 0xed, 0xf4, 0x3c, 0xe2,
0x8d, 0xe6, 0x53, 0xd6, 0xc7, 0xb0, 0x73, 0x0c, 0x5b, 0xd4, 0xc7, 0xec, 0x2c, 0xd1, 0x3b, 0x70, 0x0f, 0x67, 0x13, 0xde, 0xd5, 0xf0, 0x4b, 0x0d, 0x9f, 0x17, 0xa7, 0xf8, 0x22, 0xb1, 0x7b, 0xb0,
0x1b, 0x54, 0xb2, 0x6d, 0xb6, 0xa8, 0xfc, 0x61, 0x48, 0x22, 0x37, 0xfb, 0x6f, 0xd0, 0x29, 0x5c, 0x4b, 0xd4, 0xaa, 0x2e, 0x77, 0xa8, 0x9d, 0xbe, 0x47, 0x7c, 0x3b, 0xfe, 0xff, 0x60, 0x13, 0x18,
0xd7, 0x52, 0xa3, 0xf2, 0x2d, 0x43, 0xfa, 0x62, 0xf6, 0x0a, 0x56, 0x97, 0x48, 0x47, 0xe0, 0xac, 0x14, 0xaa, 0x42, 0xed, 0x18, 0x2d, 0xe9, 0x1e, 0xd3, 0x0f, 0x30, 0x9a, 0x46, 0x36, 0x04, 0x6b,
0xd3, 0xb7, 0x74, 0xf9, 0x91, 0x7a, 0x57, 0x74, 0x0c, 0x37, 0x5c, 0xac, 0x92, 0xe5, 0xa7, 0xe0, 0x13, 0xbd, 0x45, 0xcb, 0xf7, 0x88, 0xde, 0xb0, 0x11, 0xdc, 0x0a, 0xb9, 0x0a, 0x97, 0x9f, 0x52,
0x1e, 0xe9, 0x10, 0x17, 0x89, 0x78, 0x17, 0xdc, 0x1b, 0xd0, 0x09, 0x40, 0xbe, 0x5e, 0x89, 0x2c, 0x50, 0xd2, 0x20, 0x21, 0x43, 0xb9, 0x96, 0x82, 0xf6, 0xd8, 0x18, 0x20, 0xd9, 0xac, 0x64, 0x9c,
0x17, 0x5c, 0x70, 0x6f, 0x48, 0x01, 0xec, 0xe7, 0xc5, 0x4b, 0x22, 0xb8, 0x67, 0x3d, 0xba, 0x5f, 0x48, 0x21, 0x05, 0xed, 0x33, 0x00, 0xf3, 0x65, 0xbe, 0x08, 0xa5, 0xa0, 0x46, 0x17, 0x0b, 0xe5,
0xce, 0xe9, 0x99, 0x8d, 0x6d, 0x2e, 0x7c, 0xf8, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xae, 0x07, 0x47, 0x7a, 0x11, 0xbd, 0xd2, 0xc1, 0x93, 0xfd, 0x65, 0x9d, 0x4f, 0xdb, 0x9a, 0xed, 0xbe, 0x8f, 0x7f,
0x1f, 0x41, 0x01, 0x00, 0x00, 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 { type UninstallReleaseResponse struct {
// Release is the release that was marked deleted. // Release is the release that was marked deleted.
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` 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{} } func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} }
@ -418,7 +420,7 @@ func (*GetVersionRequest) ProtoMessage() {}
func (*GetVersionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } func (*GetVersionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
type GetVersionResponse struct { 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{} } func (m *GetVersionResponse) Reset() { *m = GetVersionResponse{} }
@ -878,68 +880,69 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 1004 bytes of a gzipped FileDescriptorProto // 1010 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdf, 0x6f, 0xe3, 0xc4, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0x5f, 0x6f, 0xe3, 0x44,
0x13, 0xaf, 0x93, 0x34, 0x3f, 0xa6, 0x3f, 0xbe, 0xe9, 0x5e, 0xda, 0xb8, 0xd6, 0x17, 0x14, 0x19, 0x10, 0xaf, 0x93, 0x34, 0x7f, 0xa6, 0x7f, 0x48, 0xf7, 0xd2, 0xc6, 0xb5, 0x00, 0x45, 0x46, 0x70,
0xc1, 0x85, 0x83, 0x4b, 0x21, 0x3c, 0x21, 0x21, 0xa4, 0x5e, 0x2e, 0x4a, 0xcb, 0x95, 0x9c, 0xb4, 0xe1, 0xe0, 0x52, 0x08, 0x4f, 0x48, 0x08, 0xa9, 0x97, 0x8b, 0xd2, 0x42, 0xc9, 0x49, 0x1b, 0x0a,
0xa1, 0x20, 0xf1, 0x40, 0xe4, 0x26, 0x9b, 0xab, 0x39, 0xc7, 0x1b, 0xbc, 0x9b, 0xea, 0xf2, 0xce, 0x12, 0x0f, 0x44, 0x6e, 0xb2, 0xb9, 0x9a, 0x73, 0xbc, 0xc1, 0xbb, 0xa9, 0x2e, 0xef, 0xbc, 0xf0,
0x0b, 0xff, 0x06, 0xff, 0x07, 0xff, 0x13, 0xef, 0xbc, 0x20, 0xef, 0x0f, 0x37, 0x76, 0xec, 0x9c, 0x35, 0xf8, 0x1e, 0x7c, 0x27, 0xde, 0x79, 0x41, 0xde, 0x3f, 0x6e, 0xec, 0xd8, 0x39, 0x93, 0x17,
0xc9, 0x8b, 0xed, 0xdd, 0x99, 0xfd, 0xcc, 0xcc, 0x67, 0x76, 0x66, 0x12, 0xb0, 0xee, 0x9d, 0x85, 0xdb, 0xbb, 0x33, 0x3b, 0xf3, 0x9b, 0xdf, 0xec, 0xcc, 0x24, 0x60, 0xdd, 0x3b, 0x0b, 0xf7, 0x82,
0x7b, 0xc1, 0x48, 0xf0, 0xe0, 0x4e, 0x08, 0xbb, 0xe0, 0xae, 0xe7, 0x91, 0xa0, 0xb3, 0x08, 0x28, 0x91, 0xe0, 0xc1, 0x9d, 0x10, 0x76, 0xc1, 0x5d, 0xcf, 0x23, 0x41, 0x67, 0x11, 0x50, 0x4e, 0x51,
0xa7, 0xa8, 0x11, 0xca, 0x3a, 0x5a, 0xd6, 0x91, 0x32, 0xeb, 0x4c, 0x9c, 0x98, 0xdc, 0x3b, 0x01, 0x23, 0x94, 0x75, 0xb4, 0xac, 0x23, 0x65, 0xd6, 0x99, 0x38, 0x31, 0xb9, 0x77, 0x02, 0x2e, 0x9f,
0x97, 0x4f, 0xa9, 0x6d, 0x35, 0xd7, 0xf7, 0xa9, 0x3f, 0x73, 0xdf, 0x28, 0x81, 0x34, 0x11, 0x10, 0x52, 0xdb, 0x6a, 0xae, 0xef, 0x53, 0x7f, 0xe6, 0xbe, 0x56, 0x02, 0xe9, 0x22, 0x20, 0x1e, 0x71,
0x8f, 0x38, 0x8c, 0xe8, 0x77, 0xec, 0x90, 0x96, 0xb9, 0xfe, 0x8c, 0x2a, 0xc1, 0x79, 0x4c, 0xc0, 0x18, 0xd1, 0xef, 0xd8, 0x21, 0x2d, 0x73, 0xfd, 0x19, 0x55, 0x82, 0xf3, 0x98, 0x80, 0x71, 0x87,
0xb8, 0xc3, 0x97, 0x2c, 0x86, 0xf7, 0x40, 0x02, 0xe6, 0x52, 0x5f, 0xbf, 0xa5, 0xcc, 0xfe, 0xb3, 0x2f, 0x59, 0xcc, 0xde, 0x03, 0x09, 0x98, 0x4b, 0x7d, 0xfd, 0x96, 0x32, 0xfb, 0xaf, 0x02, 0x3c,
0x00, 0x4f, 0x6e, 0x5c, 0xc6, 0xb1, 0x3c, 0xc8, 0x30, 0xf9, 0x6d, 0x49, 0x18, 0x47, 0x0d, 0xd8, 0xb9, 0x71, 0x19, 0xc7, 0xf2, 0x20, 0xc3, 0xe4, 0xf7, 0x25, 0x61, 0x1c, 0x35, 0x60, 0xdf, 0x73,
0xf7, 0xdc, 0xb9, 0xcb, 0x4d, 0xa3, 0x65, 0xb4, 0x8b, 0x58, 0x2e, 0xd0, 0x19, 0x94, 0xe9, 0x6c, 0xe7, 0x2e, 0x37, 0x8d, 0x96, 0xd1, 0x2e, 0x62, 0xb9, 0x40, 0x67, 0x50, 0xa6, 0xb3, 0x19, 0x23,
0xc6, 0x08, 0x37, 0x0b, 0x2d, 0xa3, 0x5d, 0xc3, 0x6a, 0x85, 0xbe, 0x85, 0x0a, 0xa3, 0x01, 0x1f, 0xdc, 0x2c, 0xb4, 0x8c, 0x76, 0x0d, 0xab, 0x15, 0xfa, 0x16, 0x2a, 0x8c, 0x06, 0x7c, 0x7c, 0xb7,
0xdf, 0xad, 0xcc, 0x62, 0xcb, 0x68, 0x1f, 0x77, 0x3f, 0xee, 0xa4, 0x51, 0xd1, 0x09, 0x2d, 0x8d, 0x32, 0x8b, 0x2d, 0xa3, 0x7d, 0xdc, 0xfd, 0xb8, 0x93, 0x46, 0x45, 0x27, 0xf4, 0x34, 0xa2, 0x01,
0x68, 0xc0, 0x3b, 0xe1, 0xe3, 0xc5, 0x0a, 0x97, 0x99, 0x78, 0x87, 0xb8, 0x33, 0xd7, 0xe3, 0x24, 0xef, 0x84, 0x8f, 0x17, 0x2b, 0x5c, 0x66, 0xe2, 0x1d, 0xda, 0x9d, 0xb9, 0x1e, 0x27, 0x81, 0x59,
0x30, 0x4b, 0x12, 0x57, 0xae, 0xd0, 0x00, 0x40, 0xe0, 0xd2, 0x60, 0x4a, 0x02, 0x73, 0x5f, 0x40, 0x92, 0x76, 0xe5, 0x0a, 0x0d, 0x00, 0x84, 0x5d, 0x1a, 0x4c, 0x49, 0x60, 0xee, 0x0b, 0xd3, 0xed,
0xb7, 0x73, 0x40, 0xbf, 0x0e, 0xf5, 0x71, 0x8d, 0xe9, 0x4f, 0xf4, 0x0d, 0x1c, 0x4a, 0x4a, 0xc6, 0x1c, 0xa6, 0x5f, 0x85, 0xfa, 0xb8, 0xc6, 0xf4, 0x27, 0xfa, 0x06, 0x0e, 0x25, 0x25, 0xe3, 0x09,
0x13, 0x3a, 0x25, 0xcc, 0x2c, 0xb7, 0x8a, 0xed, 0xe3, 0xee, 0xb9, 0x84, 0xd2, 0x0c, 0x8f, 0x24, 0x9d, 0x12, 0x66, 0x96, 0x5b, 0xc5, 0xf6, 0x71, 0xf7, 0x5c, 0x9a, 0xd2, 0x0c, 0x8f, 0x24, 0x69,
0x69, 0x3d, 0x3a, 0x25, 0xf8, 0x40, 0xaa, 0x87, 0xdf, 0xcc, 0xfe, 0x05, 0xaa, 0x1a, 0xde, 0xee, 0x3d, 0x3a, 0x25, 0xf8, 0x40, 0xaa, 0x87, 0xdf, 0xcc, 0xfe, 0x15, 0xaa, 0xda, 0xbc, 0xdd, 0x85,
0x42, 0x59, 0x3a, 0x8f, 0x0e, 0xa0, 0x72, 0x3b, 0x7c, 0x35, 0x7c, 0xfd, 0xd3, 0xb0, 0xbe, 0x87, 0xb2, 0x04, 0x8f, 0x0e, 0xa0, 0x72, 0x3b, 0xfc, 0x7e, 0xf8, 0xea, 0xe7, 0x61, 0x7d, 0x0f, 0x55,
0xaa, 0x50, 0x1a, 0x5e, 0x7e, 0xdf, 0xaf, 0x1b, 0xe8, 0x04, 0x8e, 0x6e, 0x2e, 0x47, 0x3f, 0x8c, 0xa1, 0x34, 0xbc, 0xfc, 0xa1, 0x5f, 0x37, 0xd0, 0x09, 0x1c, 0xdd, 0x5c, 0x8e, 0x7e, 0x1c, 0xe3,
0x71, 0xff, 0xa6, 0x7f, 0x39, 0xea, 0xbf, 0xac, 0x17, 0xec, 0x0f, 0xa1, 0x16, 0x79, 0x85, 0x2a, 0xfe, 0x4d, 0xff, 0x72, 0xd4, 0x7f, 0x59, 0x2f, 0xd8, 0x1f, 0x42, 0x2d, 0x42, 0x85, 0x2a, 0x50,
0x50, 0xbc, 0x1c, 0xf5, 0xe4, 0x91, 0x97, 0xfd, 0x51, 0xaf, 0x6e, 0xd8, 0x7f, 0x18, 0xd0, 0x88, 0xbc, 0x1c, 0xf5, 0xe4, 0x91, 0x97, 0xfd, 0x51, 0xaf, 0x6e, 0xd8, 0x7f, 0x1a, 0xd0, 0x88, 0x27,
0x27, 0x81, 0x2d, 0xa8, 0xcf, 0x48, 0x98, 0x85, 0x09, 0x5d, 0xfa, 0x51, 0x16, 0xc4, 0x02, 0x21, 0x81, 0x2d, 0xa8, 0xcf, 0x48, 0x98, 0x85, 0x09, 0x5d, 0xfa, 0x51, 0x16, 0xc4, 0x02, 0x21, 0x28,
0x28, 0xf9, 0xe4, 0x9d, 0xce, 0x81, 0xf8, 0x0e, 0x35, 0x39, 0xe5, 0x8e, 0x27, 0xf8, 0x2f, 0x62, 0xf9, 0xe4, 0xad, 0xce, 0x81, 0xf8, 0x0e, 0x35, 0x39, 0xe5, 0x8e, 0x27, 0xf8, 0x2f, 0x62, 0xb9,
0xb9, 0x40, 0x5f, 0x42, 0x55, 0x05, 0xc7, 0xcc, 0x52, 0xab, 0xd8, 0x3e, 0xe8, 0x9e, 0xc6, 0x43, 0x40, 0x5f, 0x42, 0x55, 0x05, 0xc7, 0xcc, 0x52, 0xab, 0xd8, 0x3e, 0xe8, 0x9e, 0xc6, 0x43, 0x56,
0x56, 0x16, 0x71, 0xa4, 0x66, 0x0f, 0xa0, 0x39, 0x20, 0xda, 0x13, 0xc9, 0x88, 0xbe, 0x13, 0xa1, 0x1e, 0x71, 0xa4, 0x66, 0x0f, 0xa0, 0x39, 0x20, 0x1a, 0x89, 0x64, 0x44, 0xdf, 0x89, 0xd0, 0xaf,
0x5d, 0x67, 0x4e, 0x84, 0x33, 0xa1, 0x5d, 0x67, 0x4e, 0x90, 0x09, 0x15, 0x75, 0xa1, 0x84, 0x3b, 0x33, 0x27, 0x02, 0x4c, 0xe8, 0xd7, 0x99, 0x13, 0x64, 0x42, 0x45, 0x5d, 0x28, 0x01, 0x67, 0x1f,
0xfb, 0x58, 0x2f, 0x6d, 0x0e, 0xe6, 0x26, 0x90, 0x8a, 0x2b, 0x0d, 0xe9, 0x13, 0x28, 0x85, 0xd7, 0xeb, 0xa5, 0xcd, 0xc1, 0xdc, 0x34, 0xa4, 0xe2, 0x4a, 0xb3, 0xf4, 0x09, 0x94, 0xc2, 0xeb, 0x2c,
0x59, 0xc0, 0x1c, 0x74, 0x51, 0xdc, 0xcf, 0x6b, 0x7f, 0x46, 0xb1, 0x90, 0xa3, 0xff, 0x43, 0x2d, 0xcc, 0x1c, 0x74, 0x51, 0x1c, 0xe7, 0xb5, 0x3f, 0xa3, 0x58, 0xc8, 0xd1, 0xfb, 0x50, 0x0b, 0xf5,
0xd4, 0x67, 0x0b, 0x67, 0x42, 0x44, 0xb4, 0x35, 0xfc, 0xb8, 0x61, 0x5f, 0xad, 0x5b, 0xed, 0x51, 0xd9, 0xc2, 0x99, 0x10, 0x11, 0x6d, 0x0d, 0x3f, 0x6e, 0xd8, 0x57, 0xeb, 0x5e, 0x7b, 0xd4, 0xe7,
0x9f, 0x13, 0x9f, 0xef, 0xe6, 0xff, 0x0d, 0x9c, 0xa7, 0x20, 0xa9, 0x00, 0x2e, 0xa0, 0xa2, 0x5c, 0xc4, 0xe7, 0xbb, 0xe1, 0xbf, 0x81, 0xf3, 0x14, 0x4b, 0x2a, 0x80, 0x0b, 0xa8, 0x28, 0x68, 0xc2,
0x13, 0x68, 0x99, 0xbc, 0x6a, 0x2d, 0xfb, 0x2f, 0x03, 0x1a, 0xb7, 0x8b, 0xa9, 0xc3, 0x89, 0x16, 0x5a, 0x26, 0xaf, 0x5a, 0xcb, 0xfe, 0xdb, 0x80, 0xc6, 0xed, 0x62, 0xea, 0x70, 0xa2, 0x45, 0x5b,
0x6d, 0x71, 0xea, 0x29, 0xec, 0x8b, 0xb6, 0xa0, 0xb8, 0x38, 0x91, 0xd8, 0xb2, 0x77, 0xf4, 0xc2, 0x40, 0x3d, 0x85, 0x7d, 0xd1, 0x16, 0x14, 0x17, 0x27, 0xd2, 0xb6, 0xec, 0x1d, 0xbd, 0xf0, 0x89,
0x27, 0x96, 0x72, 0xf4, 0x0c, 0xca, 0x0f, 0x8e, 0xb7, 0x24, 0x4c, 0x10, 0x11, 0xb1, 0xa6, 0x34, 0xa5, 0x1c, 0x3d, 0x83, 0xf2, 0x83, 0xe3, 0x2d, 0x09, 0x13, 0x44, 0x44, 0xac, 0x29, 0x4d, 0xd1,
0x45, 0x4f, 0xc1, 0x4a, 0x03, 0x35, 0xa1, 0x32, 0x0d, 0x56, 0xe3, 0x60, 0xe9, 0x8b, 0x22, 0xab, 0x53, 0xb0, 0xd2, 0x40, 0x4d, 0xa8, 0x4c, 0x83, 0xd5, 0x38, 0x58, 0xfa, 0xa2, 0xc8, 0xaa, 0xb8,
0xe2, 0xf2, 0x34, 0x58, 0xe1, 0xa5, 0x8f, 0x3e, 0x82, 0xa3, 0xa9, 0xcb, 0x9c, 0x3b, 0x8f, 0x8c, 0x3c, 0x0d, 0x56, 0x78, 0xe9, 0xa3, 0x8f, 0xe0, 0x68, 0xea, 0x32, 0xe7, 0xce, 0x23, 0xe3, 0x7b,
0xef, 0x29, 0x7d, 0xcb, 0x44, 0x9d, 0x55, 0xf1, 0xa1, 0xda, 0xbc, 0x0a, 0xf7, 0xec, 0x2b, 0x38, 0x4a, 0xdf, 0x30, 0x51, 0x67, 0x55, 0x7c, 0xa8, 0x36, 0xaf, 0xc2, 0x3d, 0xfb, 0x0a, 0x4e, 0x13,
0x4d, 0xb8, 0xbf, 0x2b, 0x13, 0xbf, 0x1b, 0x70, 0x86, 0xa9, 0xe7, 0xdd, 0x39, 0x93, 0xb7, 0x39, 0xf0, 0x77, 0x65, 0xe2, 0x0f, 0x03, 0xce, 0x30, 0xf5, 0xbc, 0x3b, 0x67, 0xf2, 0x26, 0x07, 0x17,
0xb8, 0x58, 0x73, 0xbb, 0xb0, 0xdd, 0xed, 0xe2, 0xa6, 0xdb, 0xeb, 0xe9, 0x2d, 0xc5, 0xd3, 0xfb, 0x6b, 0xb0, 0x0b, 0xdb, 0x61, 0x17, 0x37, 0x61, 0xaf, 0xa7, 0xb7, 0x14, 0x4f, 0xef, 0x77, 0xd0,
0x1d, 0x34, 0x37, 0xbc, 0xd8, 0x35, 0xa4, 0x7f, 0x0c, 0x38, 0xbd, 0xf6, 0x19, 0x77, 0x3c, 0x2f, 0xdc, 0x40, 0xb1, 0x6b, 0x48, 0xff, 0x1a, 0x70, 0x7a, 0xed, 0x33, 0xee, 0x78, 0x5e, 0x22, 0xa2,
0x11, 0x51, 0x94, 0x49, 0x23, 0x77, 0x26, 0x0b, 0xff, 0x25, 0x93, 0xc5, 0x18, 0x25, 0x9a, 0xbf, 0x28, 0x93, 0x46, 0xee, 0x4c, 0x16, 0xfe, 0x4f, 0x26, 0x8b, 0x31, 0x4a, 0x34, 0x7f, 0xa5, 0x35,
0xd2, 0x1a, 0x7f, 0x79, 0xb2, 0x1b, 0xaf, 0xa9, 0x72, 0xa2, 0xa6, 0xd0, 0x07, 0x00, 0x01, 0x59, 0xfe, 0xf2, 0x64, 0x37, 0x5e, 0x53, 0xe5, 0x44, 0x4d, 0xa1, 0x0f, 0x00, 0x02, 0xb2, 0x64, 0x64,
0x32, 0x32, 0x16, 0xe0, 0x15, 0x71, 0xbe, 0x26, 0x76, 0x86, 0xce, 0x9c, 0xd8, 0xd7, 0x70, 0x96, 0x2c, 0x8c, 0x57, 0xc4, 0xf9, 0x9a, 0xd8, 0x19, 0x3a, 0x73, 0x62, 0x5f, 0xc3, 0x59, 0x32, 0xf8,
0x0c, 0x7e, 0x57, 0x22, 0xef, 0xa1, 0x79, 0xeb, 0xbb, 0xa9, 0x4c, 0xa6, 0xdd, 0x8d, 0x8d, 0xd8, 0x5d, 0x89, 0xbc, 0x87, 0xe6, 0xad, 0xef, 0xa6, 0x32, 0x99, 0x76, 0x37, 0x36, 0x62, 0x2b, 0xa4,
0x0a, 0x29, 0xb1, 0x35, 0x60, 0x7f, 0xb1, 0x0c, 0xde, 0x10, 0xc5, 0x95, 0x5c, 0xd8, 0xaf, 0xc0, 0xc4, 0xd6, 0x80, 0xfd, 0xc5, 0x32, 0x78, 0x4d, 0x14, 0x57, 0x72, 0x61, 0x8f, 0xc1, 0xdc, 0xf4,
0xdc, 0xb4, 0xb4, 0xab, 0xdb, 0x4f, 0xe0, 0x64, 0x40, 0xf8, 0x8f, 0xf2, 0x66, 0x29, 0x87, 0xed, 0xb4, 0x23, 0xec, 0x10, 0x5b, 0xd4, 0xba, 0x6a, 0xb2, 0x4d, 0xd9, 0x4f, 0xe0, 0x64, 0x40, 0xf8,
0x3e, 0xa0, 0xf5, 0xcd, 0x47, 0x6c, 0xb5, 0x15, 0xc7, 0xd6, 0x53, 0x59, 0xeb, 0x6b, 0x2d, 0xfb, 0x4f, 0xf2, 0xb6, 0xa9, 0x20, 0xec, 0x3e, 0xa0, 0xf5, 0xcd, 0x47, 0x7f, 0x6a, 0x2b, 0xee, 0x4f,
0x6b, 0x81, 0x7d, 0xe5, 0x32, 0x4e, 0x83, 0xd5, 0x36, 0x32, 0xea, 0x50, 0x9c, 0x3b, 0xef, 0x54, 0x4f, 0x6a, 0xad, 0x1f, 0xdd, 0xdd, 0xaf, 0x85, 0xed, 0x2b, 0x97, 0x71, 0x1a, 0xac, 0xb6, 0x11,
0x17, 0x0b, 0x3f, 0xed, 0x81, 0xf0, 0x20, 0x3a, 0xaa, 0x3c, 0x58, 0x9f, 0x09, 0x46, 0xae, 0x99, 0x54, 0x87, 0xe2, 0xdc, 0x79, 0xab, 0x3a, 0x5b, 0xf8, 0x69, 0x0f, 0x04, 0x82, 0xe8, 0xa8, 0x42,
0xd0, 0xfd, 0xbb, 0x02, 0xc7, 0xba, 0x91, 0xcb, 0xb1, 0x8b, 0x5c, 0x38, 0x5c, 0x9f, 0x58, 0xe8, 0xb0, 0x3e, 0x27, 0x8c, 0x5c, 0x73, 0xa2, 0xfb, 0x4f, 0x05, 0x8e, 0x75, 0x73, 0x97, 0xa3, 0x18,
0xd3, 0xec, 0xa9, 0x9c, 0xf8, 0x69, 0x61, 0x3d, 0xcb, 0xa3, 0x2a, 0x9d, 0xb5, 0xf7, 0xbe, 0x30, 0xb9, 0x70, 0xb8, 0x3e, 0xc5, 0xd0, 0xa7, 0xd9, 0x93, 0x3a, 0xf1, 0x73, 0xc3, 0x7a, 0x96, 0x47,
0x10, 0x83, 0x7a, 0x72, 0x90, 0xa0, 0xe7, 0xe9, 0x18, 0x19, 0x93, 0xcb, 0xea, 0xe4, 0x55, 0xd7, 0x55, 0x82, 0xb5, 0xf7, 0xbe, 0x30, 0x10, 0x83, 0x7a, 0x72, 0xb8, 0xa0, 0xe7, 0xe9, 0x36, 0x32,
0x66, 0xd1, 0x83, 0xa0, 0x3d, 0xde, 0xfd, 0xd1, 0x7b, 0x61, 0xe2, 0x03, 0xc7, 0xba, 0xc8, 0xad, 0xa6, 0x99, 0xd5, 0xc9, 0xab, 0xae, 0xdd, 0xa2, 0x07, 0x41, 0x7b, 0x7c, 0x22, 0xa0, 0x77, 0x9a,
0x1f, 0xd9, 0xfd, 0x15, 0x8e, 0x62, 0x7d, 0x16, 0x65, 0xb0, 0x95, 0x36, 0x4b, 0xac, 0xcf, 0x72, 0x89, 0x0f, 0x21, 0xeb, 0x22, 0xb7, 0x7e, 0xe4, 0xf7, 0x37, 0x38, 0x8a, 0xf5, 0x5e, 0x94, 0xc1,
0xe9, 0x46, 0xb6, 0xe6, 0x70, 0x1c, 0x2f, 0x5c, 0x94, 0x01, 0x90, 0xda, 0xdb, 0xac, 0xcf, 0xf3, 0x56, 0xda, 0x7c, 0xb1, 0x3e, 0xcb, 0xa5, 0x1b, 0xf9, 0x9a, 0xc3, 0x71, 0xbc, 0x98, 0x51, 0x86,
0x29, 0x47, 0xe6, 0x18, 0xd4, 0x93, 0x25, 0x97, 0x95, 0xc7, 0x8c, 0x26, 0x90, 0x95, 0xc7, 0xac, 0x81, 0xd4, 0x7e, 0x67, 0x7d, 0x9e, 0x4f, 0x39, 0x72, 0xc7, 0xa0, 0x9e, 0x2c, 0xc3, 0xac, 0x3c,
0x4a, 0xb6, 0xf7, 0x90, 0x03, 0xf0, 0x58, 0x85, 0xe8, 0x69, 0x66, 0x42, 0xe2, 0xc5, 0x6b, 0xb5, 0x66, 0x34, 0x86, 0xac, 0x3c, 0x66, 0x55, 0xb7, 0xbd, 0x87, 0x1c, 0x80, 0xc7, 0x2a, 0x44, 0x4f,
0xdf, 0xaf, 0x18, 0x99, 0x58, 0xc0, 0xff, 0x12, 0x93, 0x04, 0x65, 0x50, 0x93, 0x3e, 0xf6, 0xac, 0x33, 0x13, 0x12, 0x2f, 0x5e, 0xab, 0xfd, 0x6e, 0xc5, 0xc8, 0xc5, 0x02, 0xde, 0x4b, 0x4c, 0x17,
0xe7, 0x39, 0xb5, 0x13, 0x41, 0xa9, 0xc2, 0xde, 0x12, 0x54, 0xbc, 0x6b, 0x6c, 0x09, 0x2a, 0xd1, 0x94, 0x41, 0x4d, 0xfa, 0x28, 0xb4, 0x9e, 0xe7, 0xd4, 0x4e, 0x04, 0xa5, 0x0a, 0x7b, 0x4b, 0x50,
0x23, 0xec, 0xbd, 0x17, 0xf0, 0x73, 0x55, 0xeb, 0xdd, 0x95, 0xc5, 0x5f, 0x85, 0xaf, 0xfe, 0x0d, 0xf1, 0xae, 0xb1, 0x25, 0xa8, 0x44, 0x8f, 0xb0, 0xf7, 0x5e, 0xc0, 0x2f, 0x55, 0xad, 0x77, 0x57,
0x00, 0x00, 0xff, 0xff, 0x9a, 0xb0, 0xe9, 0x29, 0xfb, 0x0c, 0x00, 0x00, 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. // If the key is successfully unlocked, it will return nil.
func (s *Signatory) DecryptKey(fn PassphraseFetcher) error { 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") 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. // 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 // The Signatory must have a valid Entity.PrivateKey for this to work. If it does
// not, an error will be returned. // not, an error will be returned.
func (s *Signatory) ClearSign(chartpath string) (string, error) { 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") 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 { if fi, err := os.Stat(chartpath); err != nil {

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

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

@ -20,10 +20,11 @@ import (
"fmt" "fmt"
"testing" "testing"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
kberrs "k8s.io/kubernetes/pkg/api/errors" 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 { 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 // MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface
type MockConfigMapsInterface struct { type MockConfigMapsInterface struct {
unversioned.ConfigMapsInterface internalversion.ConfigMapInterface
objects map[string]*api.ConfigMap objects map[string]*api.ConfigMap
} }
@ -132,7 +133,7 @@ func (mock *MockConfigMapsInterface) Update(cfgmap *api.ConfigMap) (*api.ConfigM
} }
// Delete deletes a ConfigMap by name. // 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 { if _, ok := mock.objects[name]; !ok {
return kberrs.NewNotFound(api.Resource("tests"), name) return kberrs.NewNotFound(api.Resource("tests"), name)
} }

@ -139,6 +139,19 @@ func (s *Storage) History(name string) ([]*rspb.Release, error) {
return l, nil 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 // makeKey concatenates a release name and version into
// a string with format ```<release_name>#v<version>```. // a string with format ```<release_name>#v<version>```.
// This key is used to uniquely identify storage objects. // 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 { type ReleaseTestData struct {
Name string Name string
Version int32 Version int32

@ -31,8 +31,6 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver" "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. // 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 // reader must contain a YAML stream (one or more YAML documents separated
// by "\n---\n"). // by "\n---\n").
Update(namespace string, originalReader, modifiedReader io.Reader) error 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 // PrintingKubeClient implements KubeClient, but simply prints the reader to
@ -145,14 +140,6 @@ type PrintingKubeClient struct {
Out io.Writer 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. // Create prints the values of what would be created with a real KubeClient.
func (p *PrintingKubeClient) Create(ns string, r io.Reader) error { func (p *PrintingKubeClient) Create(ns string, r io.Reader) error {
_, err := io.Copy(p.Out, r) _, err := io.Copy(p.Out, r)

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

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

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

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

@ -21,15 +21,17 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"path/filepath" "path"
"regexp" "regexp"
"strings" "strings"
"google.golang.org/grpc/metadata"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
ctx "golang.org/x/net/context" 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/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/chartutil"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
@ -81,15 +83,28 @@ var ListDefaultLimit int64 = 512
// prevents an empty string from matching. // prevents an empty string from matching.
var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$") 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. // ReleaseServer implements the server-side gRPC endpoint for the HAPI services.
type ReleaseServer struct { type ReleaseServer struct {
env *environment.Environment env *environment.Environment
clientset internalclientset.Interface
} }
// NewReleaseServer creates a new release server. // NewReleaseServer creates a new release server.
func NewReleaseServer(env *environment.Environment) *ReleaseServer { func NewReleaseServer(env *environment.Environment, clientset internalclientset.Interface) *ReleaseServer {
return &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 var rel *release.Release
if req.Version <= 0 { 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 { 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 { } else {
var err error var err error
if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil { 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 // 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -504,17 +513,10 @@ func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*
return nil, nil, errInvalidRevision return nil, nil, errInvalidRevision
} }
// finds the non-deleted release with the given name crls, err := s.env.Releases.Last(req.Name)
h, err := s.env.Releases.History(req.Name)
if err != nil { if err != nil {
return nil, nil, err 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 rbv := req.Version
if req.Version == 0 { if req.Version == 0 {
@ -694,15 +696,10 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return rel, nil return rel, nil
} }
func (s *ReleaseServer) getVersionSet() (versionSet, error) { func getVersionSet(client discovery.ServerGroupsInterface) (versionSet, error) {
defVersions := newVersionSet("v1") 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 { if err != nil {
return defVersions, err return defVersions, err
} }
@ -735,7 +732,8 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
for k, v := range files { for k, v := range files {
if strings.HasSuffix(k, notesFileSuffix) { if strings.HasSuffix(k, notesFileSuffix) {
// Only apply the notes if it belongs to the parent chart // 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 notes = v
} }
delete(files, k) 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, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also // as partials are not used after renderer.Render. Empty manifests are also
// removed here. // removed here.
vs, err := s.getVersionSet() vs, err := getVersionSet(s.clientset.Discovery())
if err != nil { if err != nil {
return nil, nil, "", fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) 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) 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() rel.Info.Deleted = timeconv.Now()
res := &services.UninstallReleaseResponse{Release: rel} 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 { if err != nil {
return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) 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 // From here on out, the release is currently considered to be in Status_DELETING
// state. See https://github.com/kubernetes/helm/issues/1511 for a better way // state.
// to do this.
if err := s.env.Releases.Update(rel); err != nil { if err := s.env.Releases.Update(rel); err != nil {
log.Printf("uninstall: Failed to store updated release: %s", err) 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) 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. // Collect the errors, and return them later.
es := []string{} es := []string{}
for _, file := range files { for _, file := range filesToDelete {
b := bytes.NewBufferString(file.content) b := bytes.NewBufferString(file.content)
if err := s.env.KubeClient.Delete(rel.Namespace, b); err != nil { if err := s.env.KubeClient.Delete(rel.Namespace, b); err != nil {
log.Printf("uninstall: Failed deletion of %q: %s", req.Name, err) 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 var errs error
if len(es) > 0 { if len(es) > 0 {
errs = fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) 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" "github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
@ -50,6 +51,16 @@ data:
name: value 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 var manifestWithUpgradeHooks = `apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
@ -73,6 +84,7 @@ data:
func rsFixture() *ReleaseServer { func rsFixture() *ReleaseServer {
return &ReleaseServer{ return &ReleaseServer{
env: mockEnvironment(), env: mockEnvironment(),
clientset: fake.NewSimpleClientset(),
} }
} }
@ -168,7 +180,7 @@ func TestValidName(t *testing.T) {
func TestGetVersionSet(t *testing.T) { func TestGetVersionSet(t *testing.T) {
rs := rsFixture() rs := rsFixture()
vs, err := rs.getVersionSet() vs, err := getVersionSet(rs.clientset.Discovery())
if err != nil { if err != nil {
t.Error(err) 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) { func TestUninstallReleaseNoHooks(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() 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 major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
// Increment patch number for critical fixes to existing releases. // Increment patch number for critical fixes to existing releases.
Version = "v2.0.0" Version = "v2.1.0"
// BuildMetadata is extra build time data // BuildMetadata is extra build time data
BuildMetadata = "" BuildMetadata = ""

Loading…
Cancel
Save