Merge pull request #1 from kubernetes/master

merge for master
pull/4850/head
liaoj 7 years ago committed by GitHub
commit c6e60dabfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,12 +4,13 @@ jobs:
working_directory: /go/src/k8s.io/helm
parallelism: 3
docker:
- image: golang:1.8
- image: golang:1.10
environment:
PROJECT_NAME: "kubernetes-helm"
steps:
- checkout
- setup_remote_docker
- setup_remote_docker:
version: 17.09.0-ce
- restore_cache:
keys:
- glide-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }}

@ -34,8 +34,8 @@ else
fi
echo "Install docker client"
VER="17.03.0-ce"
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
VER="17.09.0-ce"
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
@ -48,7 +48,7 @@ echo "Configuring gcloud authentication"
echo "${GCLOUD_SERVICE_KEY}" | base64 --decode > "${HOME}/gcloud-service-key.json"
${HOME}/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file "${HOME}/gcloud-service-key.json"
${HOME}/google-cloud-sdk/bin/gcloud config set project "${PROJECT_NAME}"
docker login -e 1234@5678.com -u _json_key -p "$(cat ${HOME}/gcloud-service-key.json)" https://gcr.io
docker login -u _json_key -p "$(cat ${HOME}/gcloud-service-key.json)" https://gcr.io
echo "Building the tiller image"
make docker-build VERSION="${VERSION}"

@ -0,0 +1,9 @@
<!-- If you need help or think you have found a bug, please help us with your issue by entering the following information (otherwise you can delete this text): -->
Output of `helm version`:
Output of `kubectl version`:
Cloud Provider/Platform (AKS, GKE, Minikube etc.):

@ -5,9 +5,9 @@ The Kubernetes Helm project accepts contributions via GitHub pull requests. This
## Reporting a Security Issue
Most of the time, when you find a bug in Helm, it should be reported
using [GitHub issues](github.com/kubernetes/helm/issues). However, if
using [GitHub issues](https://github.com/kubernetes/helm/issues). However, if
you are reporting a _security vulnerability_, please email a report to
[helm-security@deis.com](mailto:helm-security@deis.com). This will give
[cncf-kubernetes-helm-security@lists.cncf.io](mailto:cncf-kubernetes-helm-security@lists.cncf.io). This will give
us a chance to try to fix the issue before it is exploited in the wild.
## Contributor License Agreements
@ -30,7 +30,7 @@ apply to [third_party](third_party/) and [vendor](vendor/).
Whether you are a user or contributor, official support channels include:
- GitHub [issues](https://github.com/kubenetes/helm/issues/new)
- GitHub [issues](https://github.com/kubernetes/helm/issues/new)
- Slack: #Helm room in the [Kubernetes Slack](http://slack.kubernetes.io/)
Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of.

@ -12,7 +12,7 @@ PKG := $(shell glide novendor)
TAGS :=
TESTS := .
TESTFLAGS :=
LDFLAGS :=
LDFLAGS := -w -s
GOFLAGS :=
BINDIR := $(CURDIR)/bin
BINARIES := helm tiller
@ -31,7 +31,7 @@ build:
.PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static"
build-cross:
CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP)
CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) $(if $(TAGS),-tags '$(TAGS)',) -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP)
.PHONY: dist
dist:
@ -120,7 +120,6 @@ coverage:
HAS_GLIDE := $(shell command -v glide;)
HAS_GOX := $(shell command -v gox;)
HAS_GIT := $(shell command -v git;)
HAS_HG := $(shell command -v hg;)
.PHONY: bootstrap
bootstrap:
@ -133,9 +132,6 @@ endif
ifndef HAS_GIT
$(error You must install Git)
endif
ifndef HAS_HG
$(error You must install Mercurial)
endif
glide install --strip-vendor
go build -o bin/protoc-gen-go ./vendor/github.com/golang/protobuf/protoc-gen-go

@ -4,14 +4,11 @@ maintainers:
- jascott1
- mattfarina
- michelleN
- migmartri
- nebril
- prydonius
- seh
- SlickNik
- technosophos
- thomastaylor312
- vaikas-google
- viglesiasce
reviewers:
- adamreese
@ -23,10 +20,11 @@ reviewers:
- migmartri
- nebril
- prydonius
- sebgoa
- seh
- SlickNik
- technosophos
- thomastaylor312
- vaikas-google
- viglesiasce
emeritus:
- migmartri
- seh
- vaikas-google

@ -32,15 +32,14 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install
Binary downloads of the Helm client can be found at the following links:
- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-darwin-amd64.tar.gz)
- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-linux-amd64.tar.gz)
- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-linux-386.tar.gz)
- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-windows-amd64.tar.gz)
Binary downloads of the Helm client can be found on [the latest Releases page](https://github.com/kubernetes/helm/releases/latest).
Unpack the `helm` binary and add it to your PATH and you are good to go!
macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`.
If you want to use a package manager:
- macOS/[homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`.
- Windows/[chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide).
@ -59,11 +58,13 @@ The [Helm roadmap uses Github milestones](https://github.com/kubernetes/helm/mil
You can reach the Helm community and developers via the following channels:
- [Kubernetes Slack](https://slack.k8s.io):
- [Kubernetes Slack](http://slack.k8s.io):
- #helm-users
- #helm-dev
- #charts
- Mailing List: https://groups.google.com/forum/#!forum/kubernetes-sig-apps
- Mailing Lists:
- [Helm Mailing List](https://lists.cncf.io/g/cncf-kubernetes-helm)
- [Kubernetes SIG Apps Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-apps)
- Developer Call: Thursdays at 9:30-10:00 Pacific. [https://zoom.us/j/4526666954](https://zoom.us/j/4526666954)
### Code of conduct

@ -34,10 +34,12 @@ message Hook {
POST_ROLLBACK = 8;
RELEASE_TEST_SUCCESS = 9;
RELEASE_TEST_FAILURE = 10;
CRD_INSTALL = 11;
}
enum DeletePolicy {
SUCCEEDED = 0;
FAILED = 1;
BEFORE_HOOK_CREATION = 2;
}
string name = 1;
// Kind is the Kubernetes kind.

@ -165,7 +165,7 @@ message GetReleaseStatusResponse {
// Info contains information about the release.
hapi.release.Info info = 2;
// Namesapce the release was released into
// Namespace the release was released into
string namespace = 3;
}
@ -271,6 +271,8 @@ message InstallReleaseRequest {
// wait, if true, will wait until all Pods, PVCs, and Services are in a ready state
// before marking the release as successful. It will wait for as long as timeout
bool wait = 9;
bool disable_crd_hook = 10;
}
// InstallReleaseResponse is the response from a release installation.

@ -33,7 +33,7 @@ func TestCreateCmd(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Remove(tdir)
defer os.RemoveAll(tdir)
// CD into it
pwd, err := os.Getwd()
@ -46,7 +46,7 @@ func TestCreateCmd(t *testing.T) {
defer os.Chdir(pwd)
// Run a create
cmd := newCreateCmd(os.Stdout)
cmd := newCreateCmd(ioutil.Discard)
if err := cmd.RunE(cmd, []string{cname}); err != nil {
t.Errorf("Failed to run create: %s", err)
return
@ -79,7 +79,7 @@ func TestCreateStarterCmd(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Remove(tdir)
defer os.RemoveAll(tdir)
thome, err := tempHelmHome(t)
if err != nil {
@ -117,7 +117,7 @@ func TestCreateStarterCmd(t *testing.T) {
defer os.Chdir(pwd)
// Run a create
cmd := newCreateCmd(os.Stdout)
cmd := newCreateCmd(ioutil.Discard)
cmd.ParseFlags([]string{"--starter", "starterchart"})
if err := cmd.RunE(cmd, []string{cname}); err != nil {
t.Errorf("Failed to run create: %s", err)

@ -57,7 +57,7 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command {
SuggestFor: []string{"remove", "rm"},
Short: "given a release name, delete the release from Kubernetes",
Long: deleteDesc,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("command 'delete' requires a release name")

@ -16,59 +16,43 @@ limitations under the License.
package main
import (
"bytes"
"strings"
"io"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
)
func TestDependencyListCmd(t *testing.T) {
tests := []struct {
name string
args []string
expect string
err bool
}{
tests := []releaseCase{
{
name: "No such chart",
args: []string{"/no/such/chart"},
err: true,
},
{
name: "No requirements.yaml",
args: []string{"testdata/testcharts/alpine"},
expect: "WARNING: no requirements at ",
name: "No requirements.yaml",
args: []string{"testdata/testcharts/alpine"},
expected: "WARNING: no requirements at ",
},
{
name: "Requirements in chart dir",
args: []string{"testdata/testcharts/reqtest"},
expect: "NAME \tVERSION\tREPOSITORY \tSTATUS \n" +
expected: "NAME \tVERSION\tREPOSITORY \tSTATUS \n" +
"reqsubchart \t0.1.0 \thttps://example.com/charts\tunpacked\n" +
"reqsubchart2\t0.2.0 \thttps://example.com/charts\tunpacked\n" +
"reqsubchart3\t>=0.1.0\thttps://example.com/charts\tok \n\n",
},
{
name: "Requirements in chart archive",
args: []string{"testdata/testcharts/reqtest-0.1.0.tgz"},
expect: "NAME \tVERSION\tREPOSITORY \tSTATUS \nreqsubchart \t0.1.0 \thttps://example.com/charts\tmissing\nreqsubchart2\t0.2.0 \thttps://example.com/charts\tmissing\n",
name: "Requirements in chart archive",
args: []string{"testdata/testcharts/reqtest-0.1.0.tgz"},
expected: "NAME \tVERSION\tREPOSITORY \tSTATUS \nreqsubchart \t0.1.0 \thttps://example.com/charts\tmissing\nreqsubchart2\t0.2.0 \thttps://example.com/charts\tmissing\n",
},
}
for _, tt := range tests {
buf := bytes.NewBuffer(nil)
dlc := newDependencyListCmd(buf)
if err := dlc.RunE(dlc, tt.args); err != nil {
if tt.err {
continue
}
t.Errorf("Test %q: %s", tt.name, err)
continue
}
got := buf.String()
if !strings.Contains(got, tt.expect) {
t.Errorf("Test: %q, Expected:\n%q\nGot:\n%q", tt.name, tt.expect, got)
}
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newDependencyListCmd(out)
})
}

@ -52,6 +52,8 @@ type fetchCmd struct {
destdir string
version string
repoURL string
username string
password string
verify bool
verifyLater bool
@ -106,6 +108,8 @@ func newFetchCmd(out io.Writer) *cobra.Command {
f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.StringVar(&fch.username, "username", "", "chart repository username")
f.StringVar(&fch.password, "password", "", "chart repository password")
return cmd
}
@ -117,6 +121,8 @@ func (f *fetchCmd) run() error {
Keyring: f.keyring,
Verify: downloader.VerifyNever,
Getters: getter.All(settings),
Username: f.username,
Password: f.password,
}
if f.verify {
@ -138,7 +144,7 @@ func (f *fetchCmd) run() error {
}
if f.repoURL != "" {
chartURL, err := repo.FindChartInRepoURL(f.repoURL, f.chartRef, f.version, f.certFile, f.keyFile, f.caFile, getter.All(settings))
chartURL, err := repo.FindChartInAuthRepoURL(f.repoURL, f.username, f.password, f.chartRef, f.version, f.certFile, f.keyFile, f.caFile, getter.All(settings))
if err != nil {
return err
}

@ -57,7 +57,7 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "get [flags] RELEASE_NAME",
Short: "download a named release",
Long: getHelp,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired

@ -47,7 +47,7 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "hooks [flags] RELEASE_NAME",
Short: "download all hooks for a named release",
Long: getHooksHelp,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired

@ -49,15 +49,13 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "manifest [flags] RELEASE_NAME",
Short: "download the manifest for a named release",
Long: getManifestHelp,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired
}
get.release = args[0]
if get.client == nil {
get.client = helm.NewClient(helm.Host(settings.TillerHost))
}
get.client = ensureHelmClient(get.client)
return get.run()
},
}

@ -47,7 +47,7 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "values [flags] RELEASE_NAME",
Short: "download the values file for a named release",
Long: getValuesHelp,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired

@ -17,7 +17,6 @@ limitations under the License.
package main // import "k8s.io/helm/cmd/helm"
import (
"errors"
"fmt"
"io/ioutil"
"log"
@ -25,12 +24,14 @@ import (
"strings"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
// Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/helm/pkg/helm"
helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/portforwarder"
@ -166,7 +167,7 @@ func markDeprecated(cmd *cobra.Command, notice string) *cobra.Command {
return cmd
}
func setupConnection(c *cobra.Command, args []string) error {
func setupConnection() error {
if settings.TillerHost == "" {
config, client, err := getKubeClient(settings.KubeContext)
if err != nil {
@ -209,18 +210,21 @@ func checkArgsLength(argsReceived int, requiredArgs ...string) error {
// prettyError unwraps or rewrites certain errors to make them more user-friendly.
func prettyError(err error) error {
// Add this check can prevent the object creation if err is nil.
if err == nil {
return nil
}
// This is ridiculous. Why is 'grpc.rpcError' not exported? The least they
// could do is throw an interface on the lib that would let us get back
// the desc. Instead, we have to pass ALL errors through this.
return errors.New(grpc.ErrorDesc(err))
// If it's grpc's error, make it more user-friendly.
if s, ok := status.FromError(err); ok {
return fmt.Errorf(s.Message())
}
// Else return the original error.
return err
}
// configForContext creates a Kubernetes REST client configuration for a given kubeconfig context.
func configForContext(context string) (*rest.Config, error) {
config, err := kube.GetConfig(context, settings.KubeConfig).ClientConfig()
config, err := kube.GetConfig(context).ClientConfig()
if err != nil {
return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err)
}
@ -264,7 +268,7 @@ func ensureHelmClient(h helm.Interface) helm.Interface {
}
func newClient() helm.Interface {
options := []helm.Option{helm.Host(settings.TillerHost)}
options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)}
if tlsVerify || tlsEnable {
if tlsCaCertFile == "" {

@ -40,23 +40,25 @@ type releaseCmd func(c *helm.FakeClient, out io.Writer) *cobra.Command
// runReleaseCases runs a set of release cases through the given releaseCmd.
func runReleaseCases(t *testing.T, tests []releaseCase, rcmd releaseCmd) {
var buf bytes.Buffer
for _, tt := range tests {
c := &helm.FakeClient{
Rels: tt.rels,
}
cmd := rcmd(c, &buf)
cmd.ParseFlags(tt.flags)
err := cmd.RunE(cmd, tt.args)
if (err != nil) != tt.err {
t.Errorf("%q. expected error, got '%v'", tt.name, err)
}
re := regexp.MustCompile(tt.expected)
if !re.Match(buf.Bytes()) {
t.Errorf("%q. expected\n%q\ngot\n%q", tt.name, tt.expected, buf.String())
}
buf.Reset()
t.Run(tt.name, func(t *testing.T) {
c := &helm.FakeClient{
Rels: tt.rels,
Responses: tt.responses,
}
cmd := rcmd(c, &buf)
cmd.ParseFlags(tt.flags)
err := cmd.RunE(cmd, tt.args)
if (err != nil) != tt.err {
t.Errorf("expected error, got '%v'", err)
}
re := regexp.MustCompile(tt.expected)
if !re.Match(buf.Bytes()) {
t.Errorf("expected\n%q\ngot\n%q", tt.expected, buf.String())
}
buf.Reset()
})
}
}
@ -70,7 +72,8 @@ type releaseCase struct {
err bool
resp *release.Release
// Rels are the available releases at the start of the test.
rels []*release.Release
rels []*release.Release
responses map[string]release.TestRun_Status
}
// tempHelmHome sets up a Helm Home in a temp dir.

@ -17,9 +17,11 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
@ -29,6 +31,16 @@ import (
"k8s.io/helm/pkg/timeconv"
)
type releaseInfo struct {
Revision int32 `json:"revision"`
Updated string `json:"updated"`
Status string `json:"status"`
Chart string `json:"chart"`
Description string `json:"description"`
}
type releaseHistory []releaseInfo
var historyHelp = `
History prints historical revisions for a given release.
@ -46,11 +58,12 @@ The historical release set is printed as a formatted table, e.g:
`
type historyCmd struct {
max int32
rls string
out io.Writer
helmc helm.Interface
colWidth uint
max int32
rls string
out io.Writer
helmc helm.Interface
colWidth uint
outputFormat string
}
func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
@ -61,7 +74,7 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
Long: historyHelp,
Short: "fetch release history",
Aliases: []string{"hist"},
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
switch {
case len(args) == 0:
@ -77,6 +90,7 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
f := cmd.Flags()
f.Int32Var(&his.max, "max", 256, "maximum number of revision to include in history")
f.UintVar(&his.colWidth, "col-width", 60, "specifies the max column width of output")
f.StringVarP(&his.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
return cmd
}
@ -90,15 +104,31 @@ func (cmd *historyCmd) run() error {
return nil
}
fmt.Fprintln(cmd.out, formatHistory(r.Releases, cmd.colWidth))
releaseHistory := getReleaseHistory(r.Releases)
var history []byte
var formattingError error
switch cmd.outputFormat {
case "yaml":
history, formattingError = yaml.Marshal(releaseHistory)
case "json":
history, formattingError = json.Marshal(releaseHistory)
case "table":
history = formatAsTable(releaseHistory, cmd.colWidth)
default:
return fmt.Errorf("unknown output format %q", cmd.outputFormat)
}
if formattingError != nil {
return prettyError(formattingError)
}
fmt.Fprintln(cmd.out, string(history))
return nil
}
func formatHistory(rls []*release.Release, colWidth uint) string {
tbl := uitable.New()
tbl.MaxColWidth = colWidth
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION")
func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
for i := len(rls) - 1; i >= 0; i-- {
r := rls[i]
c := formatChartname(r.Chart)
@ -106,9 +136,30 @@ func formatHistory(rls []*release.Release, colWidth uint) string {
s := r.Info.Status.Code.String()
v := r.Version
d := r.Info.Description
tbl.AddRow(v, t, s, c, d)
rInfo := releaseInfo{
Revision: v,
Updated: t,
Status: s,
Chart: c,
Description: d,
}
history = append(history, rInfo)
}
return history
}
func formatAsTable(releases releaseHistory, colWidth uint) []byte {
tbl := uitable.New()
tbl.MaxColWidth = colWidth
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION")
for i := 0; i <= len(releases)-1; i++ {
r := releases[i]
tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.Description)
}
return tbl.String()
return tbl.Bytes()
}
func formatChartname(c *chart.Chart) string {

@ -17,10 +17,11 @@ limitations under the License.
package main
import (
"bytes"
"regexp"
"io"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
rpb "k8s.io/helm/pkg/proto/hapi/release"
)
@ -34,50 +35,51 @@ func TestHistoryCmd(t *testing.T) {
})
}
tests := []struct {
cmds string
desc string
args []string
resp []*rpb.Release
xout string
}{
tests := []releaseCase{
{
cmds: "helm history RELEASE_NAME",
desc: "get history for release",
name: "get history for release",
args: []string{"angry-bird"},
resp: []*rpb.Release{
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
mk("angry-bird", 2, rpb.Status_SUPERSEDED),
mk("angry-bird", 1, rpb.Status_SUPERSEDED),
},
xout: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
expected: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
},
{
cmds: "helm history --max=MAX RELEASE_NAME",
desc: "get history with max limit set",
args: []string{"--max=2", "angry-bird"},
resp: []*rpb.Release{
name: "get history with max limit set",
args: []string{"angry-bird"},
flags: []string{"--max", "2"},
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
},
xout: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
expected: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
},
{
name: "get history with yaml output format",
args: []string{"angry-bird"},
flags: []string{"--output", "yaml"},
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
},
expected: "- chart: foo-0.1.0-beta.1\n description: Release mock\n revision: 3\n status: SUPERSEDED\n updated: (.*)\n- chart: foo-0.1.0-beta.1\n description: Release mock\n revision: 4\n status: DEPLOYED\n updated: (.*)\n\n",
},
{
name: "get history with json output format",
args: []string{"angry-bird"},
flags: []string{"--output", "json"},
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
},
expected: `[{"revision":3,"updated":".*","status":"SUPERSEDED","chart":"foo\-0.1.0-beta.1","description":"Release mock"},{"revision":4,"updated":".*","status":"DEPLOYED","chart":"foo\-0.1.0-beta.1","description":"Release mock"}]\n`,
},
}
var buf bytes.Buffer
for _, tt := range tests {
frc := &helm.FakeClient{Rels: tt.resp}
cmd := newHistoryCmd(frc, &buf)
cmd.ParseFlags(tt.args)
if err := cmd.RunE(cmd, tt.args); err != nil {
t.Fatalf("%q\n\t%s: unexpected error: %v", tt.cmds, tt.desc, err)
}
re := regexp.MustCompile(tt.xout)
if !re.Match(buf.Bytes()) {
t.Fatalf("%q\n\t%s:\nexpected\n\t%q\nactual\n\t%q", tt.cmds, tt.desc, tt.xout, buf.String())
}
buf.Reset()
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newHistoryCmd(c, out)
})
}

@ -23,6 +23,7 @@ import (
"fmt"
"io"
"os"
"time"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -33,6 +34,7 @@ import (
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/repo"
)
@ -86,6 +88,7 @@ type initCmd struct {
kubeClient kubernetes.Interface
serviceAccount string
maxHistory int
replicas int
wait bool
}
@ -130,6 +133,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host")
f.StringVar(&i.serviceAccount, "service-account", "", "name of service account")
f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
f.IntVar(&i.replicas, "replicas", 1, "amount of tiller instances to run on the cluster")
f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)")
f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)")
@ -175,6 +179,7 @@ func (i *initCmd) run() error {
i.opts.ForceUpgrade = i.forceUpgrade
i.opts.ServiceAccount = i.serviceAccount
i.opts.MaxHistory = i.maxHistory
i.opts.Replicas = i.replicas
writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error {
w := i.out
@ -307,10 +312,12 @@ func (i *initCmd) run() error {
"(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)")
}
} else {
if err := i.ping(); err != nil {
return err
}
fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.")
fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.\n\n"+
"Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+
"For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation")
}
if err := i.ping(); err != nil {
return err
}
} else {
fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set")
@ -322,6 +329,19 @@ func (i *initCmd) run() error {
func (i *initCmd) ping() error {
if i.wait {
_, kubeClient, err := getKubeClient(settings.KubeContext)
if err != nil {
return err
}
if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout) {
return fmt.Errorf("tiller was not found. polling deadline exceeded")
}
// establish a connection to Tiller now that we've effectively guaranteed it's available
if err := setupConnection(); err != nil {
return err
}
i.client = newClient()
if err := i.client.PingTiller(); err != nil {
return fmt.Errorf("could not ping Tiller: %s", err)
}
@ -362,11 +382,11 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) err
if fi, err := os.Stat(repoFile); err != nil {
fmt.Fprintf(out, "Creating %s \n", repoFile)
f := repo.NewRepoFile()
sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh)
sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh, home)
if err != nil {
return err
}
lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out)
lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out, home)
if err != nil {
return err
}
@ -381,7 +401,7 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) err
return nil
}
func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool) (*repo.Entry, error) {
func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL)
c := repo.Entry{
Name: stableRepository,
@ -406,7 +426,7 @@ func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool) (*repo.En
return &c, nil
}
func initLocalRepo(indexFile, cacheFile string, out io.Writer) (*repo.Entry, error) {
func initLocalRepo(indexFile, cacheFile string, out io.Writer, home helmpath.Home) (*repo.Entry, error) {
if fi, err := os.Stat(indexFile); err != nil {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL)
i := repo.NewIndexFile()
@ -415,7 +435,9 @@ func initLocalRepo(indexFile, cacheFile string, out io.Writer) (*repo.Entry, err
}
//TODO: take this out and replace with helm update functionality
createLink(indexFile, cacheFile)
if err := createLink(indexFile, cacheFile, home); err != nil {
return nil, err
}
} else if fi.IsDir() {
return nil, fmt.Errorf("%s must be a file, not a directory", indexFile)
}
@ -438,3 +460,34 @@ func ensureRepoFileFormat(file string, out io.Writer) error {
return nil
}
// watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we
// want to wait before we call New().
//
// Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false.
func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64) bool {
deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C
checkTillerPodTicker := time.NewTicker(500 * time.Millisecond)
doneChan := make(chan bool)
defer checkTillerPodTicker.Stop()
go func() {
for range checkTillerPodTicker.C {
_, err := portforwarder.GetTillerPodName(client.CoreV1(), namespace)
if err == nil {
doneChan <- true
break
}
}
}()
for {
select {
case <-deadlinePollingChan:
return false
case <-doneChan:
return true
}
}
}

@ -46,7 +46,7 @@ func TestInitCmd(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset()
@ -80,7 +80,7 @@ func TestInitCmd_exists(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset(&v1beta1.Deployment{
@ -113,7 +113,7 @@ func TestInitCmd_clientOnly(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset()
@ -184,7 +184,7 @@ func TestEnsureHome(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
defer os.RemoveAll(home)
b := bytes.NewBuffer(nil)
hh := helmpath.Home(home)

@ -20,8 +20,10 @@ package main
import (
"os"
"k8s.io/helm/pkg/helm/helmpath"
)
func createLink(indexFile, cacheFile string) {
os.Symlink(indexFile, cacheFile)
func createLink(indexFile, cacheFile string, home helmpath.Home) error {
return os.Symlink(indexFile, cacheFile)
}

@ -20,8 +20,10 @@ package main
import (
"os"
"k8s.io/helm/pkg/helm/helmpath"
)
func createLink(indexFile, cacheFile string) {
os.Link(indexFile, cacheFile)
func createLink(indexFile, cacheFile string, home helmpath.Home) error {
return os.Link(indexFile, cacheFile)
}

@ -19,11 +19,14 @@ package main
import (
"fmt"
"io"
"strings"
"github.com/ghodss/yaml"
"github.com/golang/protobuf/ptypes/any"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/kubernetes/pkg/util/slice"
)
const inspectDesc = `
@ -43,6 +46,11 @@ This command inspects a chart (directory, file, or URL) and displays the content
of the Charts.yaml file
`
const readmeChartDesc = `
This command inspects a chart (directory, file, or URL) and displays the contents
of the README file
`
type inspectCmd struct {
chartpath string
output string
@ -51,6 +59,8 @@ type inspectCmd struct {
out io.Writer
version string
repoURL string
username string
password string
certFile string
keyFile string
@ -60,13 +70,16 @@ type inspectCmd struct {
const (
chartOnly = "chart"
valuesOnly = "values"
both = "both"
readmeOnly = "readme"
all = "all"
)
var readmeFileNames = []string{"readme.md", "readme.txt", "readme"}
func newInspectCmd(out io.Writer) *cobra.Command {
insp := &inspectCmd{
out: out,
output: both,
output: all,
}
inspectCommand := &cobra.Command{
@ -77,7 +90,7 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, args[0], insp.version, insp.verify, insp.keyring,
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err
@ -96,7 +109,7 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, args[0], insp.version, insp.verify, insp.keyring,
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err
@ -115,7 +128,26 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, args[0], insp.version, insp.verify, insp.keyring,
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err
}
insp.chartpath = cp
return insp.run()
},
}
readmeSubCmd := &cobra.Command{
Use: "readme [CHART]",
Short: "shows inspect readme",
Long: readmeChartDesc,
RunE: func(cmd *cobra.Command, args []string) error {
insp.output = readmeOnly
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err
@ -125,51 +157,65 @@ func newInspectCmd(out io.Writer) *cobra.Command {
},
}
cmds := []*cobra.Command{inspectCommand, readmeSubCmd, valuesSubCmd, chartSubCmd}
vflag := "verify"
vdesc := "verify the provenance data for this chart"
inspectCommand.Flags().BoolVar(&insp.verify, vflag, false, vdesc)
valuesSubCmd.Flags().BoolVar(&insp.verify, vflag, false, vdesc)
chartSubCmd.Flags().BoolVar(&insp.verify, vflag, false, vdesc)
for _, subCmd := range cmds {
subCmd.Flags().BoolVar(&insp.verify, vflag, false, vdesc)
}
kflag := "keyring"
kdesc := "path to the keyring containing public verification keys"
kdefault := defaultKeyring()
inspectCommand.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
valuesSubCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
chartSubCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
}
verflag := "version"
verdesc := "version of the chart. By default, the newest chart is shown"
inspectCommand.Flags().StringVar(&insp.version, verflag, "", verdesc)
valuesSubCmd.Flags().StringVar(&insp.version, verflag, "", verdesc)
chartSubCmd.Flags().StringVar(&insp.version, verflag, "", verdesc)
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.version, verflag, "", verdesc)
}
repoURL := "repo"
repoURLdesc := "chart repository url where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc)
valuesSubCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc)
chartSubCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc)
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc)
}
username := "username"
usernamedesc := "chart repository username where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.username, username, "", usernamedesc)
valuesSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc)
chartSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc)
password := "password"
passworddesc := "chart repository password where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.password, password, "", passworddesc)
valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
certFile := "cert-file"
certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle"
inspectCommand.Flags().StringVar(&insp.certFile, certFile, "", certFiledesc)
valuesSubCmd.Flags().StringVar(&insp.certFile, certFile, "", certFiledesc)
chartSubCmd.Flags().StringVar(&insp.certFile, certFile, "", certFiledesc)
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.certFile, certFile, "", certFiledesc)
}
keyFile := "key-file"
keyFiledesc := "identify HTTPS client using this SSL key file"
inspectCommand.Flags().StringVar(&insp.keyFile, keyFile, "", keyFiledesc)
valuesSubCmd.Flags().StringVar(&insp.keyFile, keyFile, "", keyFiledesc)
chartSubCmd.Flags().StringVar(&insp.keyFile, keyFile, "", keyFiledesc)
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.keyFile, keyFile, "", keyFiledesc)
}
caFile := "ca-file"
caFiledesc := "chart repository url where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc)
valuesSubCmd.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc)
chartSubCmd.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc)
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc)
}
inspectCommand.AddCommand(valuesSubCmd)
inspectCommand.AddCommand(chartSubCmd)
for _, subCmd := range cmds[1:] {
inspectCommand.AddCommand(subCmd)
}
return inspectCommand
}
@ -184,16 +230,35 @@ func (i *inspectCmd) run() error {
return err
}
if i.output == chartOnly || i.output == both {
if i.output == chartOnly || i.output == all {
fmt.Fprintln(i.out, string(cf))
}
if (i.output == valuesOnly || i.output == both) && chrt.Values != nil {
if i.output == both {
if (i.output == valuesOnly || i.output == all) && chrt.Values != nil {
if i.output == all {
fmt.Fprintln(i.out, "---")
}
fmt.Fprintln(i.out, chrt.Values.Raw)
}
if i.output == readmeOnly || i.output == all {
if i.output == all {
fmt.Fprintln(i.out, "---")
}
readme := findReadme(chrt.Files)
if readme == nil {
return nil
}
fmt.Fprintln(i.out, string(readme.Value))
}
return nil
}
func findReadme(files []*any.Any) (file *any.Any) {
for _, file := range files {
if slice.ContainsString(readmeFileNames, strings.ToLower(file.TypeUrl), nil) {
return file
}
}
return nil
}

@ -28,7 +28,7 @@ func TestInspect(t *testing.T) {
insp := &inspectCmd{
chartpath: "testdata/testcharts/alpine",
output: "both",
output: all,
out: b,
}
insp.run()
@ -42,15 +42,19 @@ func TestInspect(t *testing.T) {
if err != nil {
t.Fatal(err)
}
parts := strings.SplitN(b.String(), "---", 2)
if len(parts) != 2 {
readmeData, err := ioutil.ReadFile("testdata/testcharts/alpine/README.md")
if err != nil {
t.Fatal(err)
}
parts := strings.SplitN(b.String(), "---", 3)
if len(parts) != 3 {
t.Fatalf("Expected 2 parts, got %d", len(parts))
}
expect := []string{
strings.Replace(strings.TrimSpace(string(cdata)), "\r", "", -1),
strings.Replace(strings.TrimSpace(string(data)), "\r", "", -1),
strings.Replace(strings.TrimSpace(string(readmeData)), "\r", "", -1),
}
// Problem: ghodss/yaml doesn't marshal into struct order. To solve, we
@ -73,5 +77,4 @@ func TestInspect(t *testing.T) {
if b.Len() != 0 {
t.Errorf("expected empty values buffer, got %q", b.String())
}
}

@ -50,7 +50,8 @@ The install argument must be a chart reference, a path to a packaged chart,
a path to an unpacked chart directory or a URL.
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, to force
a string value use '--set-string'.
$ helm install -f myvalues.yaml ./redis
@ -58,6 +59,10 @@ or
$ helm install --set name=prod ./redis
or
$ helm install --set-string long_int=1234567890 ./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:
@ -101,25 +106,29 @@ charts in a repository, use 'helm search'.
`
type installCmd struct {
name string
namespace string
valueFiles valueFiles
chartPath string
dryRun bool
disableHooks bool
replace bool
verify bool
keyring string
out io.Writer
client helm.Interface
values []string
nameTemplate string
version string
timeout int64
wait bool
repoURL string
devel bool
depUp bool
name string
namespace string
valueFiles valueFiles
chartPath string
dryRun bool
disableHooks bool
disableCRDHook bool
replace bool
verify bool
keyring string
out io.Writer
client helm.Interface
values []string
stringValues []string
nameTemplate string
version string
timeout int64
wait bool
repoURL string
username string
password string
devel bool
depUp bool
certFile string
keyFile string
@ -153,7 +162,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
Use: "install [CHART]",
Short: "install a chart archive",
Long: installDesc,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
@ -165,7 +174,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
inst.version = ">0.0.0-0"
}
cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring,
cp, err := locateChartPath(inst.repoURL, inst.username, inst.password, args[0], inst.version, inst.verify, inst.keyring,
inst.certFile, inst.keyFile, inst.caFile)
if err != nil {
return err
@ -182,8 +191,10 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&inst.disableCRDHook, "no-crd-hook", false, "prevent CRD hooks from running, but run other hooks")
f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it")
f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
@ -191,6 +202,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
@ -207,7 +220,7 @@ func (i *installCmd) run() error {
i.namespace = defaultNamespace()
}
rawVals, err := vals(i.valueFiles, i.values)
rawVals, err := vals(i.valueFiles, i.values, i.stringValues, i.certFile, i.keyFile, i.caFile)
if err != nil {
return err
}
@ -229,7 +242,7 @@ func (i *installCmd) run() error {
}
if req, err := chartutil.LoadRequirements(chartRequested); err == nil {
// If checkDependencies returns an error, we have unfullfilled dependencies.
// If checkDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/kubernetes/helm/issues/2209
if err := checkDependencies(chartRequested, req); err != nil {
@ -262,6 +275,7 @@ func (i *installCmd) run() error {
helm.InstallDryRun(i.dryRun),
helm.InstallReuseName(i.replace),
helm.InstallDisableHooks(i.disableHooks),
helm.InstallDisableCRDHook(i.disableCRDHook),
helm.InstallTimeout(i.timeout),
helm.InstallWait(i.wait))
if err != nil {
@ -276,6 +290,10 @@ func (i *installCmd) run() error {
// If this is a dry run, we can't display status.
if i.dryRun {
// This is special casing to avoid breaking backward compatibility:
if res.Release.Info.Description != "Dry run complete" {
fmt.Fprintf(os.Stdout, "WARNING: %s\n", res.Release.Info.Description)
}
return nil
}
@ -302,11 +320,6 @@ func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[st
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
@ -321,8 +334,8 @@ func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[st
}
// vals merges values from files specified via -f/--values and
// directly via --set, marshaling them to YAML
func vals(valueFiles valueFiles, values []string) ([]byte, error) {
// directly via --set or --set-string, marshaling them to YAML
func vals(valueFiles valueFiles, values []string, stringValues []string, CertFile, KeyFile, CAFile string) ([]byte, error) {
base := map[string]interface{}{}
// User specified a values files via -f/--values
@ -334,7 +347,7 @@ func vals(valueFiles valueFiles, values []string) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" {
bytes, err = ioutil.ReadAll(os.Stdin)
} else {
bytes, err = readFile(filePath)
bytes, err = readFile(filePath, CertFile, KeyFile, CAFile)
}
if err != nil {
@ -355,6 +368,13 @@ func vals(valueFiles valueFiles, values []string) ([]byte, error) {
}
}
// User specified a value via --set-string
for _, value := range stringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err)
}
}
return yaml.Marshal(base)
}
@ -381,7 +401,7 @@ func (i *installCmd) printRelease(rel *release.Release) {
// - URL
//
// If 'verify' is true, this will attempt to also verify the chart.
func locateChartPath(repoURL, name, version string, verify bool, keyring,
func locateChartPath(repoURL, username, password, name, version string, verify bool, keyring,
certFile, keyFile, caFile string) (string, error) {
name = strings.TrimSpace(name)
version = strings.TrimSpace(version)
@ -414,12 +434,14 @@ func locateChartPath(repoURL, name, version string, verify bool, keyring,
Out: os.Stdout,
Keyring: keyring,
Getters: getter.All(settings),
Username: username,
Password: password,
}
if verify {
dl.Verify = downloader.VerifyAlways
}
if repoURL != "" {
chartURL, err := repo.FindChartInRepoURL(repoURL, name, version,
chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version,
certFile, keyFile, caFile, getter.All(settings))
if err != nil {
return "", err
@ -443,7 +465,7 @@ func locateChartPath(repoURL, name, version string, verify bool, keyring,
return filename, err
}
return filename, fmt.Errorf("failed to download %q", name)
return filename, fmt.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
}
func generateName(nameTemplate string) (string, error) {
@ -460,7 +482,7 @@ func generateName(nameTemplate string) (string, error) {
}
func defaultNamespace() string {
if ns, _, err := kube.GetConfig(settings.KubeContext, settings.KubeConfig).Namespace(); err == nil {
if ns, _, err := kube.GetConfig(settings.KubeContext).Namespace(); err == nil {
return ns
}
return "default"
@ -490,7 +512,7 @@ func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error {
}
//readFile load a file from the local directory or a remote file with a url.
func readFile(filePath string) ([]byte, error) {
func readFile(filePath, CertFile, KeyFile, CAFile string) ([]byte, error) {
u, _ := url.Parse(filePath)
p := getter.All(settings)
@ -499,12 +521,12 @@ func readFile(filePath string) ([]byte, error) {
if err != nil {
return ioutil.ReadFile(filePath)
} else {
getter, err := getterConstructor(filePath, "", "", "")
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err
}
getter, err := getterConstructor(filePath, CertFile, KeyFile, CAFile)
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err
}

@ -63,8 +63,8 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
if err != nil {
return err
}
existingImage := obj.Spec.Template.Spec.Containers[0].Image
if !isNewerVersion(existingImage) && !opts.ForceUpgrade {
tillerImage := obj.Spec.Template.Spec.Containers[0].Image
if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
}
obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
@ -73,7 +73,7 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
return err
}
// If the service does not exists that would mean we are upgrading from a Tiller version
// If the service does not exist that would mean we are upgrading from a Tiller version
// that didn't deploy the service, so install it.
_, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
@ -82,15 +82,24 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
return err
}
// isNewerVersion returns whether the current version is newer than the give image's version
func isNewerVersion(image string) bool {
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
func semverCompare(image string) int {
split := strings.Split(image, ":")
if len(split) < 2 {
// If we don't know the version, we consider the current version newer
return true
// If we don't know the version, we consider the client version newer.
return 1
}
imageVersion := split[1]
return semver.MustParse(version.Version).GreaterThan(semver.MustParse(imageVersion))
tillerVersion, err := semver.NewVersion(split[1])
if err != nil {
// same thing with unparsable tiller versions (e.g. canary releases).
return 1
}
clientVersion, err := semver.NewVersion(version.Version)
if err != nil {
// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
return 1
}
return clientVersion.Compare(tillerVersion)
}
// createDeployment creates the Tiller Deployment resource.
@ -174,6 +183,7 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
Labels: labels,
},
Spec: v1beta1.DeploymentSpec{
Replicas: opts.getReplicas(),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,

@ -211,6 +211,10 @@ func TestInstall(t *testing.T) {
if ports != 2 {
t.Errorf("expected ports = 2, got '%d'", ports)
}
replicas := obj.Spec.Replicas
if int(*replicas) != 1 {
t.Errorf("expected replicas = 1, got '%d'", replicas)
}
return true, obj, nil
})
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
@ -236,6 +240,29 @@ func TestInstall(t *testing.T) {
}
}
func TestInstallHA(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
replicas := obj.Spec.Replicas
if int(*replicas) != 2 {
t.Errorf("expected replicas = 2, got '%d'", replicas)
}
return true, obj, nil
})
opts := &Options{
Namespace: v1.NamespaceDefault,
ImageSpec: image,
Replicas: 2,
}
if err := Install(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
}
func TestInstall_WithTLS(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
name := "tiller-secret"
@ -476,6 +503,129 @@ func TestUgrade_newerVersion(t *testing.T) {
}
}
func TestUpgrade_identical(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v2.0.0",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestUpgrade_canaryClient(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:canary"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v1.0.0",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestUpgrade_canaryServer(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:canary",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func tlsTestFile(t *testing.T, path string) string {
const tlsTestDir = "../../../testdata"
path = filepath.Join(tlsTestDir, path)

@ -81,6 +81,11 @@ type Options struct {
// Less than or equal to zero means no limit.
MaxHistory int
// Replicas sets the amount of Tiller replicas to start
//
// Less than or equals to 1 means 1.
Replicas int
// NodeSelectors determine which nodes Tiller can land on.
NodeSelectors string
@ -109,6 +114,14 @@ func (opts *Options) pullPolicy() v1.PullPolicy {
return v1.PullIfNotPresent
}
func (opts *Options) getReplicas() *int32 {
replicas := int32(1)
if opts.Replicas > 1 {
replicas = int32(opts.Replicas)
}
return &replicas
}
func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS }
// valuesMap returns user set values in map format

@ -46,6 +46,7 @@ or recommendation, it will emit [WARNING] messages.
type lintCmd struct {
valueFiles valueFiles
values []string
sValues []string
namespace string
strict bool
paths []string
@ -71,7 +72,8 @@ func newLintCmd(out io.Writer) *cobra.Command {
cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)")
cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to put the release into")
cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings")
return cmd
@ -99,6 +101,9 @@ func (l *lintCmd) run() error {
if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil {
fmt.Println("==> Skipping", path)
fmt.Println(err)
if err == errLintNoChart {
failures = failures + 1
}
} else {
fmt.Println("==> Linting", path)
@ -149,7 +154,11 @@ func lintChart(path string, vals []byte, namespace string, strict bool) (support
return linter, err
}
base := strings.Split(filepath.Base(path), "-")[0]
lastHyphenIndex := strings.LastIndex(filepath.Base(path), "-")
if lastHyphenIndex <= 0 {
return linter, fmt.Errorf("unable to parse chart archive %q, missing '-'", filepath.Base(path))
}
base := filepath.Base(path)[:lastHyphenIndex]
chartPath = filepath.Join(tempDir, base)
} else {
chartPath = path
@ -188,5 +197,12 @@ func (l *lintCmd) vals() ([]byte, error) {
}
}
// User specified a value via --set-string
for _, value := range l.sValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err)
}
}
return yaml.Marshal(base)
}

@ -21,11 +21,14 @@ import (
)
var (
values = []byte{}
namespace = "testNamespace"
strict = false
archivedChartPath = "testdata/testcharts/compressedchart-0.1.0.tgz"
chartDirPath = "testdata/testcharts/decompressedchart/"
values = []byte{}
namespace = "testNamespace"
strict = false
archivedChartPath = "testdata/testcharts/compressedchart-0.1.0.tgz"
archivedChartPathWithHyphens = "testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz"
invalidArchivedChartPath = "testdata/testcharts/invalidcompressedchart0.1.0.tgz"
chartDirPath = "testdata/testcharts/decompressedchart/"
chartMissingManifest = "testdata/testcharts/chart-missing-manifest"
)
func TestLintChart(t *testing.T) {
@ -37,4 +40,15 @@ func TestLintChart(t *testing.T) {
t.Errorf("%s", err)
}
if _, err := lintChart(archivedChartPathWithHyphens, values, namespace, strict); err != nil {
t.Errorf("%s", err)
}
if _, err := lintChart(invalidArchivedChartPath, values, namespace, strict); err == nil {
t.Errorf("Expected a chart parsing error")
}
if _, err := lintChart(chartMissingManifest, values, namespace, strict); err == nil {
t.Errorf("Expected a chart parsing error")
}
}

@ -88,7 +88,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
Short: "list releases",
Long: listHelp,
Aliases: []string{"ls"},
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
list.filter = strings.Join(args, " ")
@ -148,7 +148,7 @@ func (l *listCmd) run() error {
return prettyError(err)
}
if len(res.Releases) == 0 {
if len(res.GetReleases()) == 0 {
return nil
}
@ -237,14 +237,19 @@ func formatList(rels []*release.Release, colWidth uint) string {
table := uitable.New()
table.MaxColWidth = colWidth
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE")
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "NAMESPACE")
for _, r := range rels {
c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version)
t := timeconv.String(r.Info.LastDeployed)
s := r.Info.Status.Code.String()
v := r.Version
n := r.Namespace
table.AddRow(r.Name, v, t, s, c, n)
md := r.GetChart().GetMetadata()
c := fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion())
t := "-"
if tspb := r.GetInfo().GetLastDeployed(); tspb != nil {
t = timeconv.String(tspb)
}
s := r.GetInfo().GetStatus().GetCode().String()
v := r.GetVersion()
a := md.GetAppVersion()
n := r.GetNamespace()
table.AddRow(r.GetName(), v, t, s, c, a, n)
}
return table.String()
}

@ -17,50 +17,69 @@ limitations under the License.
package main
import (
"bytes"
"regexp"
"io"
"testing"
"github.com/spf13/cobra"
"io/ioutil"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
"os"
)
func TestListCmd(t *testing.T) {
tests := []struct {
name string
args []string
resp []*release.Release
expected string
err bool
}{
tmpChart, _ := ioutil.TempDir("testdata", "tmp")
defer os.RemoveAll(tmpChart)
cfile := &chart.Metadata{
Name: "foo",
Description: "A Helm chart for Kubernetes",
Version: "0.1.0-beta.1",
AppVersion: "2.X.A",
}
chartPath, err := chartutil.Create(cfile, tmpChart)
if err != nil {
t.Errorf("Error creating chart for list: %v", err)
}
ch, _ := chartutil.Load(chartPath)
tests := []releaseCase{
{
name: "with a release",
resp: []*release.Release{
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
},
expected: "thomas-guide",
},
{
name: "list",
args: []string{},
resp: []*release.Release{
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}),
},
expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n",
expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tAPP VERSION\tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\t \tdefault \n",
},
{
name: "list, one deployed, one failed",
args: []string{"-q"},
resp: []*release.Release{
name: "list with appVersion",
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas", Chart: ch}),
},
expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tAPP VERSION\tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\t2.X.A \tdefault \n",
},
{
name: "list, one deployed, one failed",
flags: []string{"-q"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_FAILED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expected: "thomas-guide\natlas-guide",
},
{
name: "with a release, multiple flags",
args: []string{"--deleted", "--deployed", "--failed", "-q"},
resp: []*release.Release{
name: "with a release, multiple flags",
flags: []string{"--deleted", "--deployed", "--failed", "-q"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
@ -69,9 +88,9 @@ func TestListCmd(t *testing.T) {
expected: "thomas-guide\natlas-guide",
},
{
name: "with a release, multiple flags",
args: []string{"--all", "-q"},
resp: []*release.Release{
name: "with a release, multiple flags",
flags: []string{"--all", "-q"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
@ -79,9 +98,9 @@ func TestListCmd(t *testing.T) {
expected: "thomas-guide\natlas-guide",
},
{
name: "with a release, multiple flags, deleting",
args: []string{"--all", "-q"},
resp: []*release.Release{
name: "with a release, multiple flags, deleting",
flags: []string{"--all", "-q"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETING}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
@ -89,9 +108,9 @@ func TestListCmd(t *testing.T) {
expected: "thomas-guide\natlas-guide",
},
{
name: "namespace defined, multiple flags",
args: []string{"--all", "-q", "--namespace test123"},
resp: []*release.Release{
name: "namespace defined, multiple flags",
flags: []string{"--all", "-q", "--namespace test123"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Namespace: "test123"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Namespace: "test321"}),
},
@ -99,18 +118,18 @@ func TestListCmd(t *testing.T) {
expected: "thomas-guide",
},
{
name: "with a pending release, multiple flags",
args: []string{"--all", "-q"},
resp: []*release.Release{
name: "with a pending release, multiple flags",
flags: []string{"--all", "-q"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expected: "thomas-guide\natlas-guide",
},
{
name: "with a pending release, pending flag",
args: []string{"--pending", "-q"},
resp: []*release.Release{
name: "with a pending release, pending flag",
flags: []string{"--pending", "-q"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "wild-idea", StatusCode: release.Status_PENDING_UPGRADE}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-maps", StatusCode: release.Status_PENDING_ROLLBACK}),
@ -120,7 +139,7 @@ func TestListCmd(t *testing.T) {
},
{
name: "with old releases",
resp: []*release.Release{
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_FAILED}),
},
@ -128,21 +147,7 @@ func TestListCmd(t *testing.T) {
},
}
var buf bytes.Buffer
for _, tt := range tests {
c := &helm.FakeClient{
Rels: tt.resp,
}
cmd := newListCmd(c, &buf)
cmd.ParseFlags(tt.args)
err := cmd.RunE(cmd, tt.args)
if (err != nil) != tt.err {
t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err)
}
re := regexp.MustCompile(tt.expected)
if !re.Match(buf.Bytes()) {
t.Errorf("%q. expected\n%q\ngot\n%q", tt.name, tt.expected, buf.String())
}
buf.Reset()
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newListCmd(c, out)
})
}

@ -103,7 +103,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
if _, err := processParent(cmd, args); err != nil {
return err
}
return setupConnection(cmd, args)
return setupConnection()
}
}
@ -131,7 +131,7 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
switch a := args[i]; a {
case "--debug":
known = append(known, a)
case "--host", "--kube-context", "--home":
case "--host", "--kube-context", "--home", "--tiller-namespace":
known = append(known, a, args[i+1])
i++
default:

@ -205,7 +205,19 @@ func TestSetAppVersion(t *testing.T) {
var ch *chart.Chart
expectedAppVersion := "app-version-foo"
tmp, _ := ioutil.TempDir("", "helm-package-app-version-")
defer os.RemoveAll(tmp)
thome, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
os.RemoveAll(tmp)
os.RemoveAll(thome.String())
cleanup()
}()
settings.Home = helmpath.Home(thome)
c := newPackageCmd(&bytes.Buffer{})
flags := map[string]string{
@ -213,7 +225,7 @@ func TestSetAppVersion(t *testing.T) {
"app-version": expectedAppVersion,
}
setFlags(c, flags)
err := c.RunE(c, []string{"testdata/testcharts/alpine"})
err = c.RunE(c, []string{"testdata/testcharts/alpine"})
if err != nil {
t.Errorf("unexpected error %q", err)
}

@ -51,7 +51,7 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
Use: "test [RELEASE]",
Short: "test a release",
Long: releaseTestDesc,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name"); err != nil {
return err

@ -17,55 +17,50 @@ limitations under the License.
package main
import (
"bytes"
"io"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestReleaseTesting(t *testing.T) {
tests := []struct {
name string
args []string
flags []string
responses map[string]release.TestRun_Status
fail bool
}{
tests := []releaseCase{
{
name: "basic test",
args: []string{"example-release"},
flags: []string{},
responses: map[string]release.TestRun_Status{"PASSED: green lights everywhere": release.TestRun_SUCCESS},
fail: false,
err: false,
},
{
name: "test failure",
args: []string{"example-fail"},
flags: []string{},
responses: map[string]release.TestRun_Status{"FAILURE: red lights everywhere": release.TestRun_FAILURE},
fail: true,
err: true,
},
{
name: "test unknown",
args: []string{"example-unknown"},
flags: []string{},
responses: map[string]release.TestRun_Status{"UNKNOWN: yellow lights everywhere": release.TestRun_UNKNOWN},
fail: false,
err: false,
},
{
name: "test error",
args: []string{"example-error"},
flags: []string{},
responses: map[string]release.TestRun_Status{"ERROR: yellow lights everywhere": release.TestRun_FAILURE},
fail: true,
err: true,
},
{
name: "test running",
args: []string{"example-running"},
flags: []string{},
responses: map[string]release.TestRun_Status{"RUNNING: things are happpeningggg": release.TestRun_RUNNING},
fail: false,
err: false,
},
{
name: "multiple tests example",
@ -78,29 +73,11 @@ func TestReleaseTesting(t *testing.T) {
"FAILURE: good thing u checked :)": release.TestRun_FAILURE,
"RUNNING: things are happpeningggg yet again": release.TestRun_RUNNING,
"PASSED: feel free to party again": release.TestRun_SUCCESS},
fail: true,
err: true,
},
}
for _, tt := range tests {
c := &helm.FakeClient{Responses: tt.responses}
buf := bytes.NewBuffer(nil)
cmd := newReleaseTestCmd(c, buf)
cmd.ParseFlags(tt.flags)
err := cmd.RunE(cmd, tt.args)
if err == nil && tt.fail {
t.Errorf("%q did not fail but should have failed", tt.name)
}
if err != nil {
if tt.fail {
continue
} else {
t.Errorf("%q reported error: %s", tt.name, err)
}
}
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newReleaseTestCmd(c, out)
})
}

@ -30,6 +30,8 @@ import (
type repoAddCmd struct {
name string
url string
username string
password string
home helmpath.Home
noupdate bool
@ -60,6 +62,8 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&add.username, "username", "", "chart repository username")
f.StringVar(&add.password, "password", "", "chart repository password")
f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered")
f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
@ -69,14 +73,14 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
}
func (a *repoAddCmd) run() error {
if err := addRepository(a.name, a.url, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil {
if err := addRepository(a.name, a.url, a.username, a.password, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil {
return err
}
fmt.Fprintf(a.out, "%q has been added to your repositories\n", a.name)
return nil
}
func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
if err != nil {
return err
@ -91,6 +95,8 @@ func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFi
Name: name,
Cache: cif,
URL: url,
Username: username,
Password: password,
CertFile: certFile,
KeyFile: keyFile,
CAFile: caFile,

@ -17,10 +17,13 @@ limitations under the License.
package main
import (
"bytes"
"io"
"os"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/repo/repotest"
)
@ -36,7 +39,7 @@ func TestRepoAddCmd(t *testing.T) {
cleanup := resetEnv()
defer func() {
srv.Stop()
os.Remove(thome.String())
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(thome, t); err != nil {
@ -49,17 +52,13 @@ func TestRepoAddCmd(t *testing.T) {
{
name: "add a repository",
args: []string{testName, srv.URL()},
expected: testName + " has been added to your repositories",
expected: "\"" + testName + "\" has been added to your repositories",
},
}
for _, tt := range tests {
buf := bytes.NewBuffer(nil)
c := newRepoAddCmd(buf)
if err := c.RunE(c, tt.args); err != nil {
t.Errorf("%q: expected %q, got %q", tt.name, tt.expected, err)
}
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newRepoAddCmd(out)
})
}
func TestRepoAdd(t *testing.T) {
@ -72,7 +71,7 @@ func TestRepoAdd(t *testing.T) {
hh := thome
defer func() {
ts.Stop()
os.Remove(thome.String())
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(hh, t); err != nil {
@ -81,7 +80,7 @@ func TestRepoAdd(t *testing.T) {
settings.Home = thome
if err := addRepository(testName, ts.URL(), hh, "", "", "", true); err != nil {
if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
@ -94,11 +93,11 @@ func TestRepoAdd(t *testing.T) {
t.Errorf("%s was not successfully inserted into %s", testName, hh.RepositoryFile())
}
if err := addRepository(testName, ts.URL(), hh, "", "", "", false); err != nil {
if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", false); err != nil {
t.Errorf("Repository was not updated: %s", err)
}
if err := addRepository(testName, ts.URL(), hh, "", "", "", false); err != nil {
if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", false); err != nil {
t.Errorf("Duplicate repository name was added")
}
}

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/spf13/cobra"
@ -86,9 +87,16 @@ func index(dir, url, mergeTo string) error {
return err
}
if mergeTo != "" {
i2, err := repo.LoadIndexFile(mergeTo)
if err != nil {
return fmt.Errorf("Merge failed: %s", err)
// if index.yaml is missing then create an empty one to merge into
var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0755)
} else {
i2, err = repo.LoadIndexFile(mergeTo)
if err != nil {
return fmt.Errorf("Merge failed: %s", err)
}
}
i.Merge(i2)
}

@ -112,6 +112,36 @@ func TestRepoIndexCmd(t *testing.T) {
if vs[0].Version != expectedVersion {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
}
// test that index.yaml gets generated on merge even when it doesn't exist
if err := os.Remove(destIndex); err != nil {
t.Fatal(err)
}
c.ParseFlags([]string{"--merge", destIndex})
if err := c.RunE(c, []string{dir}); err != nil {
t.Error(err)
}
_, err = repo.LoadIndexFile(destIndex)
if err != nil {
t.Fatal(err)
}
// verify it didn't create an empty index.yaml and the merged happened
if len(index.Entries) != 2 {
t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries)
}
vs = index.Entries["compressedchart"]
if len(vs) != 3 {
t.Errorf("expected 3 versions, got %d: %#v", len(vs), vs)
}
expectedVersion = "0.3.0"
if vs[0].Version != expectedVersion {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
}
}
func linkOrCopy(old, new string) error {

@ -41,13 +41,18 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
Aliases: []string{"rm"},
Short: "remove a chart repository",
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "name of chart repository"); err != nil {
return err
if len(args) == 0 {
return fmt.Errorf("need at least one argument, name of chart repository")
}
remove.name = args[0]
remove.home = settings.Home
return remove.run()
remove.home = settings.Home
for i := 0; i < len(args); i++ {
remove.name = args[i]
if err := remove.run(); err != nil {
return err
}
}
return nil
},
}

@ -18,6 +18,7 @@ package main
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
@ -37,7 +38,7 @@ func TestRepoRemove(t *testing.T) {
cleanup := resetEnv()
defer func() {
ts.Stop()
os.Remove(thome.String())
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(hh, t); err != nil {
@ -51,7 +52,7 @@ func TestRepoRemove(t *testing.T) {
if err := removeRepoLine(b, testName, hh); err == nil {
t.Errorf("Expected error removing %s, but did not get one.", testName)
}
if err := addRepository(testName, ts.URL(), hh, "", "", "", true); err != nil {
if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
@ -79,3 +80,66 @@ func TestRepoRemove(t *testing.T) {
t.Errorf("%s was not successfully removed from repositories list", testName)
}
}
func TestRepoRemove_NoArguments(t *testing.T) {
cmd := newRepoRemoveCmd(ioutil.Discard)
if err := cmd.RunE(cmd, []string{}); err == nil {
t.Errorf("Expected an error since no repo names were provided")
}
}
func TestRepoRemove_MultipleRepos(t *testing.T) {
ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
hh := helmpath.Home(thome)
cleanup := resetEnv()
defer func() {
ts.Stop()
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(hh, t); err != nil {
t.Fatal(err)
}
settings.Home = thome
repoFoo := testName + "foo"
repoBar := testName + "bar"
if err := addRepository(repoFoo, ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
if err := addRepository(repoBar, ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
b := bytes.NewBuffer(nil)
cmd := newRepoRemoveCmd(b)
if err := cmd.RunE(cmd, []string{repoFoo, repoBar}); err != nil {
t.Error(err)
}
if !strings.Contains(b.String(), repoFoo) {
t.Errorf("Expected %q in output, found: %q", repoFoo, b.String())
}
if !strings.Contains(b.String(), repoBar) {
t.Errorf("Expected %q in output, found: %q", repoBar, b.String())
}
f, err := repo.LoadRepositoriesFile(hh.RepositoryFile())
if err != nil {
t.Error(err)
}
if f.Has(repoFoo) {
t.Errorf("%s was not successfully removed from repositories list", repoFoo)
}
if f.Has(repoBar) {
t.Errorf("%s was not successfully removed from repositories list", repoBar)
}
}

@ -37,7 +37,7 @@ func TestUpdateCmd(t *testing.T) {
cleanup := resetEnv()
defer func() {
os.Remove(thome.String())
os.RemoveAll(thome.String())
cleanup()
}()
@ -75,7 +75,7 @@ func TestUpdateCharts(t *testing.T) {
cleanup := resetEnv()
defer func() {
ts.Stop()
os.Remove(thome.String())
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(hh, t); err != nil {

@ -58,7 +58,7 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
Short: "uninstalls Tiller from a cluster",
Long: resetDesc,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := setupConnection(cmd, args); !d.force && err != nil {
if err := setupConnection(); !d.force && err != nil {
return err
}
return nil
@ -77,7 +77,7 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state")
f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.)")
f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME")
return cmd
@ -101,7 +101,7 @@ func (d *resetCmd) run() error {
}
if !d.force && res != nil && len(res.Releases) > 0 {
return fmt.Errorf("there are still %d deployed releases (Tip: use --force)", len(res.Releases))
return fmt.Errorf("there are still %d deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)", len(res.Releases))
}
if err := installer.Uninstall(d.kubeClient, &installer.Options{Namespace: d.namespace}); err != nil {

@ -31,40 +31,58 @@ import (
"k8s.io/helm/pkg/proto/hapi/release"
)
type resetCase struct {
name string
err bool
resp []*release.Release
removeHelmHome bool
force bool
expectedActions int
expectedOutput string
}
func TestResetCmd(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
c := &helm.FakeClient{}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: core.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
actions := fc.Actions()
if len(actions) != 3 {
t.Errorf("Expected 3 actions, got %d", len(actions))
}
expected := "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
if _, err := os.Stat(home); err != nil {
t.Errorf("Helm home directory %s does not exists", home)
}
verifyResetCmd(t, resetCase{
name: "test reset command",
expectedActions: 3,
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
})
}
func TestResetCmd_removeHelmHome(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command - remove helm home",
removeHelmHome: true,
expectedActions: 3,
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
})
}
func TestReset_deployedReleases(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command - deployed releases",
resp: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
err: true,
expectedOutput: "there are still 1 deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)",
})
}
func TestReset_forceFlag(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command - force flag",
force: true,
resp: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expectedActions: 3,
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
})
}
func verifyResetCmd(t *testing.T, tc resetCase) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
@ -72,99 +90,42 @@ func TestResetCmd_removeHelmHome(t *testing.T) {
defer os.Remove(home)
var buf bytes.Buffer
c := &helm.FakeClient{}
c := &helm.FakeClient{
Rels: tc.resp,
}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
removeHelmHome: true,
removeHelmHome: tc.removeHelmHome,
force: tc.force,
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: core.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
actions := fc.Actions()
if len(actions) != 3 {
t.Errorf("Expected 3 actions, got %d", len(actions))
}
expected := "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
if _, err := os.Stat(home); err == nil {
t.Errorf("Helm home directory %s already exists", home)
}
}
func TestReset_deployedReleases(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
resp := []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
}
c := &helm.FakeClient{
Rels: resp,
}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: core.NamespaceDefault,
}
err = cmd.run()
expected := "there are still 1 deployed releases (Tip: use --force)"
if !strings.Contains(err.Error(), expected) {
if !tc.err && err != nil {
t.Errorf("unexpected error: %v", err)
}
if _, err := os.Stat(home); err != nil {
t.Errorf("Helm home directory %s does not exists", home)
}
}
func TestReset_forceFlag(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
got := buf.String()
if tc.err {
got = err.Error()
}
defer os.Remove(home)
var buf bytes.Buffer
resp := []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
}
c := &helm.FakeClient{
Rels: resp,
}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
force: true,
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: core.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
actions := fc.Actions()
if len(actions) != 3 {
t.Errorf("Expected 3 actions, got %d", len(actions))
if tc.expectedActions > 0 && len(actions) != tc.expectedActions {
t.Errorf("Expected %d actions, got %d", tc.expectedActions, len(actions))
}
if !strings.Contains(got, tc.expectedOutput) {
t.Errorf("expected %q, got %q", tc.expectedOutput, got)
}
expected := "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
_, err = os.Stat(home)
if !tc.removeHelmHome && err != nil {
t.Errorf("Helm home directory %s does not exist", home)
}
if _, err := os.Stat(home); err != nil {
t.Errorf("Helm home directory %s does not exists", home)
if tc.removeHelmHome && err == nil {
t.Errorf("Helm home directory %s exists", home)
}
}

@ -57,7 +57,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
Use: "rollback [flags] [RELEASE] [REVISION]",
Short: "roll back a release to a previous revision",
Long: rollbackDesc,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "revision number"); err != nil {
return err

@ -47,6 +47,7 @@ type searchCmd struct {
versions bool
regexp bool
version string
colWidth uint
}
func newSearchCmd(out io.Writer) *cobra.Command {
@ -66,6 +67,7 @@ func newSearchCmd(out io.Writer) *cobra.Command {
f.BoolVarP(&sc.regexp, "regexp", "r", false, "use regular expressions for searching")
f.BoolVarP(&sc.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line")
f.StringVarP(&sc.version, "version", "v", "", "search using semantic versioning constraints")
f.UintVar(&sc.colWidth, "col-width", 60, "specifies the max column width of output")
return cmd
}
@ -83,7 +85,7 @@ func (s *searchCmd) run(args []string) error {
q := strings.Join(args, " ")
res, err = index.Search(q, searchMaxScore, s.regexp)
if err != nil {
return nil
return err
}
}
@ -93,7 +95,7 @@ func (s *searchCmd) run(args []string) error {
return err
}
fmt.Fprintln(s.out, s.formatSearchResults(data))
fmt.Fprintln(s.out, s.formatSearchResults(data, s.colWidth))
return nil
}
@ -126,12 +128,12 @@ func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, err
return data, nil
}
func (s *searchCmd) formatSearchResults(res []*search.Result) string {
func (s *searchCmd) formatSearchResults(res []*search.Result, colWidth uint) string {
if len(res) == 0 {
return "No results found"
}
table := uitable.New()
table.MaxColWidth = 50
table.MaxColWidth = colWidth
table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
for _, r := range res {
table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)

@ -146,11 +146,11 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result {
term = strings.ToLower(term)
buf := []*Result{}
for k, v := range i.lines {
k = strings.ToLower(k)
v = strings.ToLower(v)
res := strings.Index(v, term)
if score := i.calcScore(res, v); res != -1 && score < threshold {
parts := strings.Split(k, verSep) // Remove version, if it is there.
lk := strings.ToLower(k)
lv := strings.ToLower(v)
res := strings.Index(lv, term)
if score := i.calcScore(res, lv); res != -1 && score < threshold {
parts := strings.Split(lk, verSep) // Remove version, if it is there.
buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
}
}

@ -91,10 +91,10 @@ var indexfileEntries = map[string]repo.ChartVersions{
},
},
{
URLs: []string{"http://example.com/charts/santa-maria-1.2.2.tgz"},
URLs: []string{"http://example.com/charts/santa-maria-1.2.2-rc-1.tgz"},
Metadata: &chart.Metadata{
Name: "santa-maria",
Version: "1.2.2",
Version: "1.2.2-RC-1",
Description: "Three boat",
},
},

@ -17,78 +17,72 @@ limitations under the License.
package main
import (
"bytes"
"strings"
"io"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
)
func TestSearchCmd(t *testing.T) {
tests := []struct {
name string
args []string
flags []string
expect string
regexp bool
fail bool
}{
tests := []releaseCase{
{
name: "search for 'maria', expect one match",
args: []string{"maria"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/mariadb\t0.3.0 \t \tChart for MariaDB",
name: "search for 'maria', expect one match",
args: []string{"maria"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/mariadb\t0.3.0 \t \tChart for MariaDB",
},
{
name: "search for 'alpine', expect two matches",
args: []string{"alpine"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod",
name: "search for 'alpine', expect two matches",
args: []string{"alpine"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alpine' with versions, expect three matches",
args: []string{"alpine"},
flags: []string{"--versions"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
name: "search for 'alpine' with versions, expect three matches",
args: []string{"alpine"},
flags: []string{"--versions"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
args: []string{"alpine"},
flags: []string{"--version", ">= 0.1, < 0.2"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
args: []string{"alpine"},
flags: []string{"--version", ">= 0.1, < 0.2"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
args: []string{"alpine"},
flags: []string{"--versions", "--version", ">= 0.1, < 0.2"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
args: []string{"alpine"},
flags: []string{"--versions", "--version", ">= 0.1, < 0.2"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alpine' with version constraint, expect one match with version 0.2.0",
args: []string{"alpine"},
flags: []string{"--version", ">= 0.1"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod",
name: "search for 'alpine' with version constraint, expect one match with version 0.2.0",
args: []string{"alpine"},
flags: []string{"--version", ">= 0.1"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alpine' with version constraint and --versions, expect two matches",
args: []string{"alpine"},
flags: []string{"--versions", "--version", ">= 0.1"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
name: "search for 'alpine' with version constraint and --versions, expect two matches",
args: []string{"alpine"},
flags: []string{"--versions", "--version", ">= 0.1"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'syzygy', expect no matches",
args: []string{"syzygy"},
expect: "No results found",
name: "search for 'syzygy', expect no matches",
args: []string{"syzygy"},
expected: "No results found",
},
{
name: "search for 'alp[a-z]+', expect two matches",
args: []string{"alp[a-z]+"},
flags: []string{"--regexp"},
expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod",
regexp: true,
name: "search for 'alp[a-z]+', expect two matches",
args: []string{"alp[a-z]+"},
flags: []string{"--regexp"},
expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alp[', expect failure to compile regexp",
args: []string{"alp["},
flags: []string{"--regexp"},
regexp: true,
fail: true,
name: "search for 'alp[', expect failure to compile regexp",
args: []string{"alp["},
flags: []string{"--regexp"},
err: true,
},
}
@ -97,19 +91,7 @@ func TestSearchCmd(t *testing.T) {
settings.Home = "testdata/helmhome"
for _, tt := range tests {
buf := bytes.NewBuffer(nil)
cmd := newSearchCmd(buf)
cmd.ParseFlags(tt.flags)
if err := cmd.RunE(cmd, tt.args); err != nil {
if tt.fail {
continue
}
t.Fatalf("%s: unexpected error %s", tt.name, err)
}
got := strings.TrimSpace(buf.String())
if got != tt.expect {
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
}
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newSearchCmd(out)
})
}

@ -63,7 +63,7 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "status [flags] RELEASE_NAME",
Short: "displays the status of the named release",
Long: statusHelp,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired

@ -17,10 +17,8 @@ limitations under the License.
package main
import (
"bytes"
"fmt"
"io"
"strings"
"testing"
"github.com/golang/protobuf/ptypes/timestamp"
@ -36,120 +34,103 @@ var (
dateString = timeconv.String(&date)
)
// statusCase describes a test case dealing with the status of a release
type statusCase struct {
name string
args []string
flags []string
expected string
err bool
rel *release.Release
}
func TestStatusCmd(t *testing.T) {
tests := []statusCase{
tests := []releaseCase{
{
name: "get status of a deployed release",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
}),
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
}),
},
},
{
name: "get status of a deployed release with notes",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nNOTES:\nrelease notes\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
},
},
{
name: "get status of a deployed release with notes in json",
args: []string{"flummoxed-chickadee"},
flags: []string{"-o", "json"},
expected: `{"name":"flummoxed-chickadee","info":{"status":{"code":1,"notes":"release notes"},"first_deployed":{"seconds":242085845},"last_deployed":{"seconds":242085845}}}`,
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
},
},
{
name: "get status of a deployed release with resources",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nRESOURCES:\nresource A\nresource B\n\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
}),
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
}),
},
},
{
name: "get status of a deployed release with resources in YAML",
args: []string{"flummoxed-chickadee"},
flags: []string{"-o", "yaml"},
expected: "info:\nfirst_deployed:\nseconds:242085845\nlast_deployed:\nseconds:242085845\nstatus:\ncode:1\nresources:|\nresourceA\nresourceB\nname:flummoxed-chickadee\n",
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
}),
expected: "info:\n (.*)first_deployed:\n (.*)seconds: 242085845\n (.*)last_deployed:\n (.*)seconds: 242085845\n (.*)status:\n code: 1\n (.*)resources: |\n (.*)resource A\n (.*)resource B\nname: flummoxed-chickadee\n",
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
}),
},
},
{
name: "get status of a deployed release with test suite",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus(
fmt.Sprintf("DEPLOYED\n\nTEST SUITE:\nLast Started: %s\nLast Completed: %s\n\n", dateString, dateString) +
"TEST \tSTATUS \tINFO \tSTARTED \tCOMPLETED \n" +
fmt.Sprintf("test run 1\tSUCCESS \textra info\t%s\t%s\n", dateString, dateString) +
fmt.Sprintf("test run 2\tFAILURE \t \t%s\t%s\n", dateString, dateString)),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
LastTestSuiteRun: &release.TestSuite{
StartedAt: &date,
CompletedAt: &date,
Results: []*release.TestRun{
{
Name: "test run 1",
Status: release.TestRun_SUCCESS,
Info: "extra info",
StartedAt: &date,
CompletedAt: &date,
},
{
Name: "test run 2",
Status: release.TestRun_FAILURE,
StartedAt: &date,
CompletedAt: &date,
"TEST \tSTATUS (.*)\tINFO (.*)\tSTARTED (.*)\tCOMPLETED (.*)\n" +
fmt.Sprintf("test run 1\tSUCCESS (.*)\textra info\t%s\t%s\n", dateString, dateString) +
fmt.Sprintf("test run 2\tFAILURE (.*)\t (.*)\t%s\t%s\n", dateString, dateString)),
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
LastTestSuiteRun: &release.TestSuite{
StartedAt: &date,
CompletedAt: &date,
Results: []*release.TestRun{
{
Name: "test run 1",
Status: release.TestRun_SUCCESS,
Info: "extra info",
StartedAt: &date,
CompletedAt: &date,
},
{
Name: "test run 2",
Status: release.TestRun_FAILURE,
StartedAt: &date,
CompletedAt: &date,
},
},
},
},
}),
}),
},
},
}
scmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command {
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newStatusCmd(c, out)
}
var buf bytes.Buffer
for _, tt := range tests {
c := &helm.FakeClient{
Rels: []*release.Release{tt.rel},
}
cmd := scmd(c, &buf)
cmd.ParseFlags(tt.flags)
err := cmd.RunE(cmd, tt.args)
if (err != nil) != tt.err {
t.Errorf("%q. expected error, got '%v'", tt.name, err)
}
})
expected := strings.Replace(tt.expected, " ", "", -1)
got := strings.Replace(buf.String(), " ", "", -1)
if expected != got {
t.Errorf("%q. expected\n%q\ngot\n%q", tt.name, expected, got)
}
buf.Reset()
}
}
func outputWithStatus(status string) string {

@ -63,17 +63,19 @@ To render just one template in a chart, use '-x':
`
type templateCmd struct {
namespace string
valueFiles valueFiles
chartPath string
out io.Writer
values []string
nameTemplate string
showNotes bool
releaseName string
renderFiles []string
kubeVersion string
outputDir string
namespace string
valueFiles valueFiles
chartPath string
out io.Writer
values []string
stringValues []string
nameTemplate string
showNotes bool
releaseName string
releaseIsUpgrade bool
renderFiles []string
kubeVersion string
outputDir string
}
func newTemplateCmd(out io.Writer) *cobra.Command {
@ -92,10 +94,12 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&t.releaseName, "name", "n", "RELEASE-NAME", "release name")
f.BoolVar(&t.releaseIsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into")
f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
@ -115,31 +119,10 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
} else {
return err
}
// verify specified templates exist relative to chart
rf := []string{}
var af string
var err error
if len(t.renderFiles) > 0 {
for _, f := range t.renderFiles {
if !filepath.IsAbs(f) {
af, err = filepath.Abs(t.chartPath + "/" + f)
if err != nil {
return fmt.Errorf("could not resolve template path: %s", err)
}
} else {
af = f
}
rf = append(rf, af)
if _, err := os.Stat(af); err != nil {
return fmt.Errorf("could not resolve template path: %s", err)
}
}
}
// verify that output-dir exists if provided
if t.outputDir != "" {
_, err = os.Stat(t.outputDir)
_, err := os.Stat(t.outputDir)
if os.IsNotExist(err) {
return fmt.Errorf("output-dir '%s' does not exist", t.outputDir)
}
@ -149,7 +132,7 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
t.namespace = defaultNamespace()
}
// get combined values and create config
rawVals, err := vals(t.valueFiles, t.values)
rawVals, err := vals(t.valueFiles, t.values, t.stringValues, "", "", "")
if err != nil {
return err
}
@ -178,6 +161,8 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
}
options := chartutil.ReleaseOptions{
Name: t.releaseName,
IsInstall: !t.releaseIsUpgrade,
IsUpgrade: t.releaseIsUpgrade,
Time: timeconv.Now(),
Namespace: t.namespace,
}
@ -230,19 +215,7 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}}
listManifests = append(listManifests, m)
}
in := func(needle string, haystack []string) bool {
// make needle path absolute
d := strings.Split(needle, "/")
dd := d[1:]
an := t.chartPath + "/" + strings.Join(dd, "/")
for _, h := range haystack {
if h == an {
return true
}
}
return false
}
if settings.Debug {
rel := &release.Release{
Name: t.releaseName,
@ -255,10 +228,45 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
printRelease(os.Stdout, rel)
}
for _, m := range tiller.SortByKind(listManifests) {
if len(t.renderFiles) > 0 && !in(m.Name, rf) {
continue
var manifestsToRender []tiller.Manifest
// if we have a list of files to render, then check that each of the
// provided files exists in the chart.
if len(t.renderFiles) > 0 {
for _, f := range t.renderFiles {
missing := true
if !filepath.IsAbs(f) {
newF, err := filepath.Abs(filepath.Join(t.chartPath, f))
if err != nil {
return fmt.Errorf("could not turn template path %s into absolute path: %s", f, err)
}
f = newF
}
for _, manifest := range listManifests {
manifestPathSplit := strings.Split(manifest.Name, string(filepath.Separator))
// remove the chart name from the path
manifestPathSplit = manifestPathSplit[1:]
toJoin := append([]string{t.chartPath}, manifestPathSplit...)
manifestPath := filepath.Join(toJoin...)
// if the filepath provided matches a manifest path in the
// chart, render that manifest
if f == manifestPath {
manifestsToRender = append(manifestsToRender, manifest)
missing = false
}
}
if missing {
return fmt.Errorf("could not find template %s in chart", f)
}
}
} else {
// no renderFiles provided, render all manifests in the chart
manifestsToRender = listManifests
}
for _, m := range tiller.SortByKind(manifestsToRender) {
data := m.Content
b := filepath.Base(m.Name)
if !t.showNotes && b == "NOTES.txt" {

@ -27,10 +27,13 @@ import (
"testing"
)
var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1"
var (
subchart1ChartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1"
frobnitzChartPath = "./../../pkg/chartutil/testdata/frobnitz"
)
func TestTemplateCmd(t *testing.T) {
absChartPath, err := filepath.Abs(chartPath)
subchart1AbsChartPath, err := filepath.Abs(subchart1ChartPath)
if err != nil {
t.Fatal(err)
}
@ -40,74 +43,109 @@ func TestTemplateCmd(t *testing.T) {
args []string
expectKey string
expectValue string
expectError string
}{
{
name: "check_name",
desc: "check for a known name in chart",
args: []string{chartPath},
args: []string{subchart1ChartPath},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: nginx",
},
{
name: "check_set_name",
desc: "verify --set values exist",
args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
args: []string{subchart1ChartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute",
desc: "verify --execute single template",
args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
args: []string{subchart1ChartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute_non_existent",
desc: "verify --execute fails on a template that doesnt exist",
args: []string{subchart1ChartPath, "-x", "templates/thisdoesntexist.yaml"},
expectError: "could not find template",
},
{
name: "check_execute_absolute",
desc: "verify --execute single template",
args: []string{chartPath, "-x", absChartPath + "/" + "templates/service.yaml", "--set", "service.name=apache"},
args: []string{subchart1ChartPath, "-x", subchart1AbsChartPath + "/" + "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute_subchart_template",
desc: "verify --execute single template on a subchart template",
args: []string{subchart1ChartPath, "-x", "charts/subcharta/templates/service.yaml", "--set", "subcharta.service.name=foobar"},
expectKey: "subchart1/charts/subcharta/templates/service.yaml",
expectValue: "protocol: TCP\n name: foobar",
},
{
name: "check_execute_subchart_template_for_tgz_subchart",
desc: "verify --execute single template on a subchart template where the subchart is a .tgz in the chart directory",
args: []string{frobnitzChartPath, "-x", "charts/mariner/templates/placeholder.tpl", "--set", "mariner.name=moon"},
expectKey: "frobnitz/charts/mariner/templates/placeholder.tpl",
expectValue: "Goodbye moon",
},
{
name: "check_namespace",
desc: "verify --namespace",
args: []string{chartPath, "--namespace", "test"},
args: []string{subchart1ChartPath, "--namespace", "test"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "namespace: \"test\"",
},
{
name: "check_release_name",
desc: "verify --release exists",
args: []string{chartPath, "--name", "test"},
args: []string{subchart1ChartPath, "--name", "test"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-name: \"test\"",
},
{
name: "check_release_is_install",
desc: "verify --is-upgrade toggles .Release.IsInstall",
args: []string{subchart1ChartPath, "--is-upgrade=false"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-is-install: \"true\"",
},
{
name: "check_release_is_upgrade",
desc: "verify --is-upgrade toggles .Release.IsUpgrade",
args: []string{subchart1ChartPath, "--is-upgrade", "true"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-is-upgrade: \"true\"",
},
{
name: "check_notes",
desc: "verify --notes shows notes",
args: []string{chartPath, "--notes", "true"},
args: []string{subchart1ChartPath, "--notes", "true"},
expectKey: "subchart1/templates/NOTES.txt",
expectValue: "Sample notes for subchart1",
},
{
name: "check_values_files",
desc: "verify --values files values exist",
args: []string{chartPath, "--values", chartPath + "/charts/subchartA/values.yaml"},
args: []string{subchart1ChartPath, "--values", subchart1ChartPath + "/charts/subchartA/values.yaml"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "name: apache",
},
{
name: "check_name_template",
desc: "verify --name-template result exists",
args: []string{chartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"},
args: []string{subchart1ChartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-name: \"foobar-YWJj-baz\"",
},
{
name: "check_kube_version",
desc: "verify --kube-version overrides the kubernetes version",
args: []string{chartPath, "--kube-version", "1.6"},
args: []string{subchart1ChartPath, "--kube-version", "1.6"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "kube-version/major: \"1\"\n kube-version/minor: \"6\"\n kube-version/gitversion: \"v1.6.0\"",
},
@ -115,7 +153,8 @@ func TestTemplateCmd(t *testing.T) {
var buf bytes.Buffer
for _, tt := range tests {
t.Run(tt.name, func(T *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
// capture stdout
old := os.Stdout
r, w, _ := os.Pipe()
@ -125,8 +164,20 @@ func TestTemplateCmd(t *testing.T) {
cmd := newTemplateCmd(out)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if err != nil {
t.Errorf("expected: %v, got %v", tt.expectValue, err)
if tt.expectError != "" {
if err == nil {
t.Errorf("expected err: %s, but no error occurred", tt.expectError)
}
// non nil error, check if it contains the expected error
if strings.Contains(err.Error(), tt.expectError) {
// had the error we were looking for, this test case is
// done
return
}
t.Fatalf("expected err: %q, got: %q", tt.expectError, err)
} else if err != nil {
t.Errorf("expected no error, got %v", err)
}
// restore stdout
w.Close()

@ -37,7 +37,8 @@ a packaged chart, or a fully qualified URL. For chart references, the latest
version will be specified unless the '--version' flag is set.
To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line.
or use the '--set' flag and pass configuration from the command line, to force string
values, use '--set-string'.
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
@ -63,6 +64,7 @@ type upgradeCmd struct {
disableHooks bool
valueFiles valueFiles
values []string
stringValues []string
verify bool
keyring string
install bool
@ -73,6 +75,8 @@ type upgradeCmd struct {
reuseValues bool
wait bool
repoURL string
username string
password string
devel bool
certFile string
@ -91,7 +95,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
Use: "upgrade [RELEASE] [CHART]",
Short: "upgrade a release",
Long: upgradeDesc,
PreRunE: setupConnection,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "chart path"); err != nil {
return err
@ -116,6 +120,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed")
f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or 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, "no-hooks", false, "disable pre/post upgrade hooks")
f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading")
@ -125,9 +130,11 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used")
f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
@ -139,7 +146,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
func (u *upgradeCmd) run() error {
chartPath, err := locateChartPath(u.repoURL, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile)
chartPath, err := locateChartPath(u.repoURL, u.username, u.password, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile)
if err != nil {
return err
}
@ -154,9 +161,15 @@ func (u *upgradeCmd) run() error {
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if err == nil {
if u.namespace == "" {
u.namespace = defaultNamespace()
}
previousReleaseNamespace := releaseHistory.Releases[0].Namespace
if previousReleaseNamespace != u.namespace {
fmt.Fprintf(u.out, "WARNING: Namespace doesn't match with previous. Release will be deployed to %s\n", previousReleaseNamespace)
fmt.Fprintf(u.out,
"WARNING: Namespace %q doesn't match with previous. Release will be deployed to %s\n",
u.namespace, previousReleaseNamespace,
)
}
}
@ -173,6 +186,7 @@ func (u *upgradeCmd) run() error {
disableHooks: u.disableHooks,
keyring: u.keyring,
values: u.values,
stringValues: u.stringValues,
namespace: u.namespace,
timeout: u.timeout,
wait: u.wait,
@ -181,7 +195,7 @@ func (u *upgradeCmd) run() error {
}
}
rawVals, err := vals(u.valueFiles, u.values)
rawVals, err := vals(u.valueFiles, u.values, u.stringValues, u.certFile, u.keyFile, u.caFile)
if err != nil {
return err
}

@ -55,6 +55,7 @@ type versionCmd struct {
showClient bool
showServer bool
short bool
template string
}
func newVersionCmd(c helm.Interface, out io.Writer) *cobra.Command {
@ -75,7 +76,7 @@ func newVersionCmd(c helm.Interface, out io.Writer) *cobra.Command {
if version.showServer {
// We do this manually instead of in PreRun because we only
// need a tunnel if server version is requested.
setupConnection(cmd, args)
setupConnection()
}
version.client = ensureHelmClient(version.client)
return version.run()
@ -85,18 +86,26 @@ func newVersionCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.BoolVarP(&version.showClient, "client", "c", false, "client version only")
f.BoolVarP(&version.showServer, "server", "s", false, "server version only")
f.BoolVar(&version.short, "short", false, "print the version number")
f.StringVar(&version.template, "template", "", "template for version string format")
return cmd
}
func (v *versionCmd) run() error {
// Store map data for template rendering
data := map[string]interface{}{}
if v.showClient {
cv := version.GetVersionProto()
fmt.Fprintf(v.out, "Client: %s\n", formatVersion(cv, v.short))
if v.template != "" {
data["Client"] = cv
} else {
fmt.Fprintf(v.out, "Client: %s\n", formatVersion(cv, v.short))
}
}
if !v.showServer {
return nil
return tpl(v.template, data, v.out)
}
if settings.Debug {
@ -115,8 +124,13 @@ func (v *versionCmd) run() error {
debug("%s", err)
return errors.New("cannot connect to Tiller")
}
fmt.Fprintf(v.out, "Server: %s\n", formatVersion(resp.Version, v.short))
return nil
if v.template != "" {
data["Server"] = resp.Version
} else {
fmt.Fprintf(v.out, "Server: %s\n", formatVersion(resp.Version, v.short))
}
return tpl(v.template, data, v.out)
}
func getK8sVersion() (*apiVersion.Info, error) {

@ -16,49 +16,50 @@ limitations under the License.
package main
import (
"bytes"
"strings"
"fmt"
"io"
"regexp"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/version"
)
func TestVersion(t *testing.T) {
lver := regexp.QuoteMeta(version.GetVersionProto().SemVer)
sver := regexp.QuoteMeta("1.2.3-fakeclient+testonly")
clientVersion := fmt.Sprintf("Client: &version\\.Version{SemVer:\"%s\", GitCommit:\"\", GitTreeState:\"\"}\n", lver)
serverVersion := fmt.Sprintf("Server: &version\\.Version{SemVer:\"%s\", GitCommit:\"\", GitTreeState:\"\"}\n", sver)
lver := version.GetVersionProto().SemVer
sver := "1.2.3-fakeclient+testonly"
tests := []struct {
name string
client, server bool
args []string
fail bool
}{
{"default", true, true, []string{}, false},
{"client", true, false, []string{"-c"}, false},
{"server", false, true, []string{"-s"}, false},
tests := []releaseCase{
{
name: "default",
args: []string{},
expected: clientVersion + serverVersion,
},
{
name: "client",
args: []string{},
flags: []string{"-c"},
expected: clientVersion,
},
{
name: "server",
args: []string{},
flags: []string{"-s"},
expected: serverVersion,
},
{
name: "template",
args: []string{},
flags: []string{"--template", "{{ .Client.SemVer }} {{ .Server.SemVer }}"},
expected: lver + " " + sver,
},
}
settings.TillerHost = "fake-localhost"
for _, tt := range tests {
b := new(bytes.Buffer)
c := &helm.FakeClient{}
cmd := newVersionCmd(c, b)
cmd.ParseFlags(tt.args)
if err := cmd.RunE(cmd, tt.args); err != nil {
if tt.fail {
continue
}
t.Fatal(err)
}
if tt.client && !strings.Contains(b.String(), lver) {
t.Errorf("Expected %q to contain %q", b.String(), lver)
}
if tt.server && !strings.Contains(b.String(), sver) {
t.Errorf("Expected %q to contain %q", b.String(), sver)
}
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newVersionCmd(c, out)
})
}

@ -33,6 +33,8 @@ import (
goprom "github.com/grpc-ecosystem/go-grpc-prometheus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive"
"k8s.io/helm/pkg/kube"
@ -113,6 +115,9 @@ func main() {
func start() {
healthSrv := health.NewServer()
healthSrv.SetServingStatus("Tiller", healthpb.HealthCheckResponse_NOT_SERVING)
clientset, err := kube.New(nil).ClientSet()
if err != nil {
logger.Fatalf("Cannot initialize Kubernetes connection: %s", err)
@ -157,13 +162,18 @@ func start() {
logger.Fatalf("Could not create server TLS configuration: %v", err)
}
opts = append(opts, grpc.Creds(credentials.NewTLS(cfg)))
opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 10 * time.Minute,
// If needed, we can configure the max connection age
}))
}
opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 10 * time.Minute,
// If needed, we can configure the max connection age
}))
opts = append(opts, grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: time.Duration(20) * time.Second, // For compatibility with the client keepalive.ClientParameters
}))
rootServer = tiller.NewServer(opts...)
healthpb.RegisterHealthServer(rootServer, healthSrv)
lstn, err := net.Listen("tcp", *grpcAddr)
if err != nil {
@ -203,6 +213,8 @@ func start() {
}
}()
healthSrv.SetServingStatus("Tiller", healthpb.HealthCheckResponse_SERVING)
select {
case err := <-srvErrCh:
logger.Fatalf("Server died: %s", err)

@ -0,0 +1,37 @@
# Custom Resource Definitions
This section of the Best Practices Guide deals with creating and using Custom Resource Definition
objects.
When working with Custom Resource Definitions (CRDs), it is important to distinguish
two different pieces:
- There is a declaration of a CRD. This is the YAML file that has the kind `CustomResourceDefinition`
- Then there are resources that _use_ the CRD. Say a CRD defines `foo.example.com/v1`. Any resource
that has `apiVersion: example.com/v1` and kind `Foo` is a resource that uses the CRD.
## Install a CRD Declaration Before Using the Resource
Helm is optimized to load as many resources into Kubernetes as fast as possible.
By design, Kubernetes can take an entire set of manifests and bring them all
online (this is called the reconciliation loop).
But there's a difference with CRDs.
For a CRD, the declaration must be registered before any resources of that CRDs
kind(s) can be used. And the registration process sometimes takes a few seconds.
### Method 1: Separate Charts
One way to do this is to put the CRD definition in one chart, and then put any
resources that use that CRD in _another_ chart.
In this method, each chart must be installed separately.
### Method 2: Pre-install Hooks
To package the two together, add a `pre-install` hook to the CRD definition so
that it is fully installed before the rest of the chart is executed.
Note that if you create the CRD with a `pre-install` hook, that CRD definition
will not be deleted when `helm delete` is run.

@ -155,7 +155,7 @@ Template comments should be used when documenting features of a template, such a
```yaml
{{- /*
mychart.shortname provides a 6 char truncated version of the release name.
*/ }}
*/ -}}
{{ define "mychart.shortname" -}}
{{ .Release.Name | trunc 6 }}
{{- end -}}

@ -1,38 +0,0 @@
# Third Party Resources
This section of the Best Practices Guide deals with creating and using Third Party Resource
objects.
When working with Third Party Resources (TPRs), it is important to distinguish
two different pieces:
- There is a declaration of a TPR. This is the YAML file that has the kind `ThirdPartyResource`
- Then there are resources that _use_ the TPR. Say a TPR defines `foo.example.com/v1`. Any resource
that has `apiVersion: example.com/v1` and kind `Foo` is a resource that uses the
TPR.
## Install a TPR Declaration Before Using the Resource
Helm is optimized to load as many resources into Kubernetes as fast as possible.
By design, Kubernetes can take an entire set of manifests and bring them all
online (this is called the reconciliation loop).
But there's a difference with TPRs.
For a TPR, the declaration must be registered before any resources of that TPRs
kind(s) can be used. And the registration process sometimes takes a few seconds.
### Method 1: Separate Charts
One way to do this is to put the TPR definition in one chart, and then put any
resources that use that TPR in _another_ chart.
In this method, each chart must be installed separately.
### Method 2: Pre-install Hooks
To package the two together, add a `pre-install` hook to the TPR definition so
that it is fully installed before the rest of the chart is executed.
Note that if you create the TPR with a `pre-install` hook, that TPR definition
will not be deleted when `helm delete` is run.

@ -92,7 +92,7 @@ There are three potential sources of values:
- A chart's `values.yaml` file
- A values file supplied by `helm install -f` or `helm upgrade -f`
- The values passed to a `--set` flag on `helm install` or `helm upgrade`
- The values passed to a `--set` or `--set-string` flag on `helm install` or `helm upgrade`
When designing the structure of your values, keep in mind that users of your
chart may want to override them via either the `-f` flag or with the `--set`

@ -148,6 +148,11 @@ Charts repository hosts its charts, so you may want to take a
**Note:** A public GCS bucket can be accessed via simple HTTPS at this address
`https://bucket-name.storage.googleapis.com/`.
### JFrog Artifactory
You can also set up chart repositories using JFrog Artifactory.
Read more about chart repositories with JFrog Artifactory [here](https://www.jfrog.com/confluence/display/RTF/Helm+Chart+Repositories)
### Github Pages example
In a similar way you can create charts repository using GitHub Pages.
@ -271,9 +276,9 @@ If the charts are backed by HTTP basic authentication, you can also supply the
username and password here:
```console
$ helm repo add fantastic-charts https://username:password@fantastic-charts.storage.googleapis.com
$ helm repo add fantastic-charts https://fantastic-charts.storage.googleapis.com --username my-username --password my-password
$ helm repo list
fantastic-charts https://username:password@fantastic-charts.storage.googleapis.com
fantastic-charts https://fantastic-charts.storage.googleapis.com
```
**Note:** A repository will not be added if it does not contain a valid

@ -3,7 +3,7 @@
## Prerequisites
* Install the [gsutil](https://cloud.google.com/storage/docs/gsutil) tool. *We rely heavily on the gsutil rsync functionality*
* Be sure to have access to the helm binary
* Be sure to have access to the Helm binary
* _Optional: We recommend you set [object versioning](https://cloud.google.com/storage/docs/gsutil/addlhelp/ObjectVersioningandConcurrencyControl#top_of_page) on your GCS bucket in case you accidentally delete something._
## Set up a local chart repository directory
@ -16,7 +16,7 @@ $ mv alpine-0.1.0.tgz fantastic-charts/
```
## Generate an updated index.yaml
Use helm to generate an updated index.yaml file by passing in the directory path and the url of the remote repository to the `helm repo index` command like this:
Use Helm to generate an updated index.yaml file by passing in the directory path and the url of the remote repository to the `helm repo index` command like this:
```console
$ helm repo index fantastic-charts/ --url https://fantastic-charts.storage.googleapis.com

@ -119,9 +119,10 @@ You have multiple options with Globs:
```yaml
{{ range $path := .Files.Glob "**.yaml" }}
{{ $path }}: |
{{ .Files.Get $path }}
{{ $root := . }}
{{ range $path, $bytes := .Files.Glob "**.yaml" }}
{{ $path }}: |-
{{ $root.Files.Get $path }}
{{ end }}
```
@ -129,7 +130,7 @@ Or
```yaml
{{ range $path, $bytes := .Files.Glob "foo/*" }}
{{ $path }}: '{{ b64enc $bytes }}'
{{ $path.base }}: '{{ $root.Files.Get $path | b64enc }}'
{{ end }}
```

@ -1,6 +1,6 @@
# Template Functions and Pipelines
So far, we've seen how to place information into a template. But that information is placed into the template unmodified. Sometimes we want to transform the supplied data in a way that makes it more useable to us.
So far, we've seen how to place information into a template. But that information is placed into the template unmodified. Sometimes we want to transform the supplied data in a way that makes it more usable to us.
Let's start with a best practice: When injecting strings from the `.Values` object into the template, we ought to quote these strings. We can do that by calling the `quote` function in the template directive:

@ -4,15 +4,19 @@ It is time to move beyond one template, and begin to create others. In this sect
In the "Flow Control" section we introduced three actions for declaring and managing templates: `define`, `template`, and `block`. In this section, we'll cover those three actions, and also introduce a special-purpose `include` function that works similarly to the `template` action.
An important detail to keep in mind when naming templates: **template names are global**. If you declare two templates with the same name, whichever one is loaded last will be the one used. Because templates in subcharts are compiled together with top-level templates, you should be careful to name your templates with _chart-specific names_.
One popular naming convention is to prefix each defined template with the name of the chart: `{{ define "mychart.labels" }}`. By using the specific chart name as a prefix we can avoid any conflicts that may arise due to two different charts that implement templates of the same name.
## Partials and `_` files
So far, we've used one file, and that one file has contained a single template. But Helm's template language allows you to create named embedded templates, that can be accessed by name elsewhere.
Before we get to the nuts-and-bolts of writing those templates, there is file naming convention that deserves mention:
- Most files in `templates/` are treated as if they contain Kubernetes manifests
- The `NOTES.txt` is one exception
- But files whose name begins with an underscore (`_`) are assumed to _not_ have a manifest inside. These files are not rendered to Kubernetes object definitions, but are available everywhere within other chart templates for use.
* Most files in `templates/` are treated as if they contain Kubernetes manifests
* The `NOTES.txt` is one exception
* But files whose name begins with an underscore (`_`) are assumed to _not_ have a manifest inside. These files are not rendered to Kubernetes object definitions, but are available everywhere within other chart templates for use.
These files are used to store partials and helpers. In fact, when we first created `mychart`, we saw a file called `_helpers.tpl`. That file is the default location for template partials.
@ -21,7 +25,7 @@ These files are used to store partials and helpers. In fact, when we first creat
The `define` action allows us to create a named template inside of a template file. Its syntax goes like this:
```yaml
{{ define "MY_NAME" }}
{{ define "MY.NAME" }}
# body of template here
{{ end }}
```
@ -29,7 +33,7 @@ The `define` action allows us to create a named template inside of a template fi
For example, we can define a template to encapsulate a Kubernetes block of labels:
```yaml
{{- define "my_labels" }}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
@ -39,7 +43,7 @@ For example, we can define a template to encapsulate a Kubernetes block of label
Now we can embed this template inside of our existing ConfigMap, and then include it with the `template` action:
```yaml
{{- define "my_labels" }}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
@ -48,7 +52,7 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "my_labels" }}
{{- template "mychart.labels" }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
@ -56,7 +60,7 @@ data:
{{- end }}
```
When the template engine reads this file, it will store away the reference to `my_labels` until `template "my_labels"` is called. Then it will render that template inline. So the result will look like this:
When the template engine reads this file, it will store away the reference to `mychart.labels` until `template "mychart.labels"` is called. Then it will render that template inline. So the result will look like this:
```yaml
# Source: mychart/templates/configmap.yaml
@ -77,7 +81,7 @@ Conventionally, Helm charts put these templates inside of a partials file, usual
```yaml
{{/* Generate basic labels */}}
{{- define "my_labels" }}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
@ -93,7 +97,7 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "my_labels" }}
{{- template "mychart.labels" }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
@ -101,9 +105,7 @@ data:
{{- end }}
```
There is one _really important detail_ to keep in mind when naming templates: **template names are global**. If you declare two templates with the same name, whichever one is loaded last will be the one used. Because templates in subcharts are compiled together with top-level templates, you should be careful to name your templates with chart-specific names.
One popular naming convention is to prefix each defined template with the name of the chart: `{{ define "mychart.labels" }}` or `{{ define "mychart_labels" }}`.
As mentioned above, **template names are global**. As a result of this, if two templates are declared with the same name the last occurrence will be the one that is used. Since templates in subcharts are compiled together with top-level templates, it is best to name your templates with _chart specific names_. A popular naming convention is to prefix each defined template with the name of the chart: `{{ define "mychart.labels" }}`.
## Setting the scope of a template
@ -111,7 +113,7 @@ In the template we defined above, we did not use any objects. We just used funct
```yaml
{{/* Generate basic labels */}}
{{- define "my_labels" }}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
@ -138,7 +140,7 @@ metadata:
What happened to the name and version? They weren't in the scope for our defined template. When a named template (created with `define`) is rendered, it will receive the scope passed in by the `template` call. In our example, we included the template like this:
```yaml
{{- template "my_labels" }}
{{- template "mychart.labels" }}
```
No scope was passed in, so within the template we cannot access anything in `.`. This is easy enough to fix, though. We simply pass a scope to the template:
@ -148,7 +150,7 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "my_labels" . }}
{{- template "mychart.labels" . }}
```
Note that we pass `.` at the end of the `template` call. We could just as easily pass `.Values` or `.Values.favorite` or whatever scope we want. But what we want is the top-level scope.
@ -174,8 +176,8 @@ Now `{{ .Chart.Name }}` resolves to `mychart`, and `{{ .Chart.Version }}` resolv
Say we've defined a simple template that looks like this:
```
{{- define "mychart_app" -}}
```yaml
{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}+{{ .Release.Time.Seconds }}"
{{- end -}}
@ -189,14 +191,13 @@ kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ template "mychart_app" .}}
{{ template "mychart.app" .}}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{ template "mychart_app" . }}
{{ template "mychart.app" . }}
```
The output will not be what we expect:
@ -230,13 +231,13 @@ kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ include "mychart_app" . | indent 4 }}
{{ include "mychart.app" . | indent 4 }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{ include "mychart_app" . | indent 2 }}
{{ include "mychart.app" . | indent 2 }}
```
Now the produced YAML is correctly indented for each section:

@ -83,7 +83,7 @@ mysubchart:
dessert: ice cream
```
Note the last two lines. Any directives inside of the `mysubchart` section will be sent to the `mysubchart` chart. So if we run `helm install --dry-run --debug mychart`, once of the things we will see is the `mysubchart` ConfigMap:
Note the last two lines. Any directives inside of the `mysubchart` section will be sent to the `mysubchart` chart. So if we run `helm install --dry-run --debug mychart`, one of the things we will see is the `mysubchart` ConfigMap:
```yaml
# Source: mychart/charts/mysubchart/templates/configmap.yaml

@ -4,7 +4,7 @@ In the previous section we looked at the built-in objects that Helm templates of
- The `values.yaml` file in the chart
- If this is a subchart, the `values.yaml` file of a parent chart
- A values file if passed into `helm install` or `helm update` with the `-f` flag (`helm install -f myvals.yaml ./mychart`)
- A values file if passed into `helm install` or `helm upgrade` with the `-f` flag (`helm install -f myvals.yaml ./mychart`)
- Individual parameters passed with `--set` (such as `helm install --set foo=bar ./mychart`)
The list above is in order of specificity: `values.yaml` is the default, which can be overridden by a parent chart's `values.yaml`, which can in turn be overridden by a user-supplied values file, which can in turn be overridden by `--set` parameters.

@ -2,7 +2,7 @@
A chart contains a number of Kubernetes resources and components that work together. As a chart author, you may want to write some tests that validate that your chart works as expected when it is installed. These tests also help the chart consumer understand what your chart is supposed to do.
A **test** in a helm chart lives under the `templates/` directory and is a pod definition that specifies a container with a given command to run. The container should exit successfully (exit 0) for a test to be considered a success. The pod definition must contain one of the helm test hook annotations: `helm.sh/hooks: test-success` or `helm.sh/hooks: test-failure`.
A **test** in a helm chart lives under the `templates/` directory and is a pod definition that specifies a container with a given command to run. The container should exit successfully (exit 0) for a test to be considered a success. The pod definition must contain one of the helm test hook annotations: `helm.sh/hook: test-success` or `helm.sh/hook: test-failure`.
Example tests:
- Validate that your configuration from the values.yaml file was properly injected.

@ -27,8 +27,8 @@ wordpress/
README.md # OPTIONAL: A human-readable README file
requirements.yaml # OPTIONAL: A YAML file listing dependencies for the chart
values.yaml # The default configuration values for this chart
charts/ # OPTIONAL: A directory containing any charts upon which this chart depends.
templates/ # OPTIONAL: A directory of templates that, when combined with values,
charts/ # A directory containing any charts upon which this chart depends.
templates/ # A directory of templates that, when combined with values,
# will generate valid Kubernetes manifest files.
templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes
```
@ -36,15 +36,15 @@ wordpress/
Helm reserves use of the `charts/` and `templates/` directories, and of
the listed file names. Other files will be left as they are.
While the `charts` and `template` directories are optional there must be at least one chart dependency or template file for the chart to be valid.
## The Chart.yaml File
The `Chart.yaml` file is required for a chart. It contains the following fields:
```yaml
apiVersion: The chart API version, always "v1" (required)
name: The name of the chart (required)
version: A SemVer 2 version (required)
kubeVersion: A SemVer range of compatible Kubernetes versions (optional)
description: A single-sentence description of this project (optional)
keywords:
- A list of keywords about this project (optional)
@ -276,7 +276,7 @@ dependencies:
condition: subchart2.enabled,global.subchart2.enabled
tags:
- back-end
- subchart1
- subchart2
````
````
@ -325,7 +325,7 @@ tooling to introspect user-settable values.
The keys containing the values to be imported can be specified in the parent chart's `requirements.yaml` file
using a YAML list. Each item in the list is a key which is imported from the child chart's `exports` field.
To import values not contained in the `exports` key, use the [child/parent](#using-the-child/parent-format) format.
To import values not contained in the `exports` key, use the [child-parent](#using-the-child-parent-format) format.
Examples of both formats are described below.
##### Using the exports format
@ -360,9 +360,9 @@ myint: 99
```
Please note the parent key `data` is not contained in the parent's final values. If you need to specify the
parent key, use the 'child/parent' format.
parent key, use the 'child-parent' format.
##### Using the child/parent format
##### Using the child-parent format
To access values that are not contained in the `exports` key of the child chart's values, you will need to
specify the source key of the values to be imported (`child`) and the destination path in the parent chart's
@ -849,7 +849,7 @@ considerations in mind:
- The `Chart.yaml` will be overwritten by the generator.
- Users will expect to modify such a chart's contents, so documentation
should indicate how users can do so.
- All occurances of `<CHARTNAME>` will be replaced with the specified chart
- All occurrences of `<CHARTNAME>` will be replaced with the specified chart
name so that starter charts can be used as templates.
Currently the only way to add a chart to `$HELM_HOME/starters` is to manually

@ -16,6 +16,17 @@ Hooks work like regular templates, but they have special annotations
that cause Helm to utilize them differently. In this section, we cover
the basic usage pattern for hooks.
Hooks are declared as an annotation in the metadata section of a manifest:
```yaml
apiVersion: ...
kind: ....
metadata:
annotations:
"helm.sh/hook": "pre-install"
# ...
```
## The Available Hooks
The following hooks are defined:
@ -36,6 +47,8 @@ The following hooks are defined:
rendered, but before any resources have been rolled back.
- post-rollback: Executes on a rollback request after all resources
have been modified.
- crd-install: Adds CRD resources before any other checks are run. This is used
only on CRD definitions that are used by other manifests in the chart.
## Hooks and the Release Lifecycle
@ -62,7 +75,7 @@ hooks, the lifecycle is altered like this:
Kubernetes)
5. Tiller sorts hooks by weight (assigning a weight of 0 by default) and by name for those hooks with the same weight in ascending order.
6. Tiller then loads the hook with the lowest weight first (negative to positive)
7. Tiller waits until the hook is "Ready"
7. Tiller waits until the hook is "Ready" (except for CRDs)
8. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait`
flag is set, Tiller will wait until all resources are in a ready state
and will not run the `post-install` hook until they are ready.
@ -180,4 +193,65 @@ It is also possible to define policies that determine when to delete correspondi
"helm.sh/hook-delete-policy": hook-succeeded
```
When using `"helm.sh/hook-delete-policy"` annotation, you can choose its value from `"hook-succeeded"` and `"hook-failed"`. The value `"hook-succeeded"` specifies Tiller should delete the hook after the hook is successfully executed, while the value `"hook-failed"`specifies Tiller should delete the hook if the hook failed during execution.
You can choose one or more defined annotation values:
* `"hook-succeeded"` specifies Tiller should delete the hook after the hook is successfully executed.
* `"hook-failed"` specifies Tiller should delete the hook if the hook failed during execution.
* `"before-hook-creation"` specifies Tiller should delete the previous hook before the new hook is launched.
### Defining a CRD with the `crd-install` Hook
Custom Resource Definitions (CRDs) are a special kind in Kubernetes. They provide
a way to define other kinds.
On occasion, a chart needs to both define a kind and then use it. This is done
with the `crd-install` hook.
The `crd-install` hook is executed very early during an installation, before
the rest of the manifests are verified. CRDs can be annotated with this hook so
that they are installed before any instances of that CRD are referenced. In this
way, when verification happens later, the CRDs will be available.
Here is an example of defining a CRD with a hook, and an instance of the CRD:
```yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: crontabs.stable.example.com
annotations:
"helm.sh/hook": crd-install
spec:
group: stable.example.com
version: v1
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
```
And:
```yaml
apiVersion: stable.example.com/v1
kind: CronTab
metadata:
name: {{ .Release.Name }}-inst
```
Both of these can now be in the same chart, provided that the CRD is correctly
annotated.
### Automatically delete hook from previous release
When helm release being updated it is possible, that hook resource already exists in cluster. By default helm will try to create resource and fail with `"... already exists"` error.
One might choose `"helm.sh/hook-delete-policy": "before-hook-creation"` over `"helm.sh/hook-delete-policy": "hook-succeeded,hook-failed"` because:
* It is convenient to keep failed hook job resource in kubernetes for example for manual debug.
* It may be necessary to keep succeeded hook resource in kubernetes for some reason.
* At the same time it is not desirable to do manual resource deletion before helm release upgrade.
`"helm.sh/hook-delete-policy": "before-hook-creation"` annotation on hook causes tiller to remove the hook from previous release if there is one before the new hook is launched and can be used with another policy.

@ -52,6 +52,16 @@ many cases, cause parsing errors inside of Kubernetes.
port: {{ .Values.Port }}
```
This remark does not apply to env variables values which are expected to be string, even if they represent integers:
```
env:
-name: HOST
value: "http://host"
-name: PORT
value: "1234"
```
## Using the 'include' Function
Go provides a way of including one template in another using a built-in
@ -204,7 +214,7 @@ together in one GitHub repository.
**Deis's [Workflow](https://github.com/deis/workflow/tree/master/charts/workflow):**
This chart exposes the entire Deis PaaS system with one chart. But it's different
from the SAP chart in that this master chart is built from each component, and
from the SAP chart in that this umbrella chart is built from each component, and
each component is tracked in a different Git repository. Check out the
`requirements.yaml` file to see how this chart is composed by their CI/CD
pipeline.
@ -234,7 +244,7 @@ update of that resource.
## Upgrade a release idempotently
In order to use the same command when installing and upgrading a release, use the following comand:
In order to use the same command when installing and upgrading a release, use the following command:
```shell
helm upgrade --install <release name> --values <values file> <chart directory>
```

@ -10,7 +10,6 @@ Helm and Tiller.
- A Kubernetes cluster w/ kubectl (optional)
- The gRPC toolchain
- Git
- Mercurial
## Building Helm/Tiller
@ -27,7 +26,7 @@ packages.
This will build both Helm and Tiller. `make bootstrap` will attempt to
install certain tools if they are missing.
To run all of the tests (without running the tests for `vendor/`), run
To run all the tests (without running the tests for `vendor/`), run
`make test`.
To run Helm and Tiller locally, you can run `bin/helm` or `bin/tiller`.

@ -1,6 +1,7 @@
name: nginx
description: A basic NGINX HTTP server
version: 0.1.0
kubeVersion: ">=1.2.0"
keywords:
- http
- nginx

@ -32,12 +32,12 @@ Environment:
### Options
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
@ -68,4 +68,4 @@ Environment:
* [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid
* [helm version](helm_version.md) - print the client/server version information
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 14-Mar-2018

@ -24,15 +24,15 @@ helm completion SHELL
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -43,15 +43,15 @@ helm create NAME
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -34,15 +34,15 @@ helm delete [flags] RELEASE_NAME [...]
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -57,12 +57,12 @@ for this case.
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
@ -71,4 +71,4 @@ for this case.
* [helm dependency list](helm_dependency_list.md) - list the dependencies for the given chart
* [helm dependency update](helm_dependency_update.md) - update charts/ based on the contents of requirements.yaml
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -30,15 +30,15 @@ helm dependency build [flags] CHART
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm dependency](helm_dependency.md) - manage a chart's dependencies
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -22,15 +22,15 @@ helm dependency list [flags] CHART
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm dependency](helm_dependency.md) - manage a chart's dependencies
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -35,15 +35,15 @@ helm dependency update [flags] CHART
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm dependency](helm_dependency.md) - manage a chart's dependencies
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -33,10 +33,12 @@ helm fetch [flags] [chart URL | repo/chartname] [...]
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
--key-file string identify HTTPS client using this SSL key file
--keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password
--prov fetch the provenance file, but don't perform verification
--repo string chart repository url where to locate the requested chart
--untar if set to true, will untar the chart after downloading it
--untardir string if untar is specified, this flag specifies the name of the directory into which the chart is expanded (default ".")
--username string chart repository username
--verify verify the package against its signature
--version string specific version of a chart. Without this, the latest version is fetched
```
@ -44,15 +46,13 @@ helm fetch [flags] [chart URL | repo/chartname] [...]
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 7-Nov-2017

@ -36,12 +36,12 @@ helm get [flags] RELEASE_NAME
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
@ -50,4 +50,4 @@ helm get [flags] RELEASE_NAME
* [helm get manifest](helm_get_manifest.md) - download the manifest for a named release
* [helm get values](helm_get_values.md) - download the values file for a named release
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -29,15 +29,15 @@ helm get hooks [flags] RELEASE_NAME
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm get](helm_get.md) - download a named release
###### Auto generated by spf13/cobra on 15-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -31,15 +31,15 @@ helm get manifest [flags] RELEASE_NAME
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm get](helm_get.md) - download a named release
###### Auto generated by spf13/cobra on 15-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -28,15 +28,15 @@ helm get values [flags] RELEASE_NAME
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm get](helm_get.md) - download a named release
###### Auto generated by spf13/cobra on 15-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -30,6 +30,7 @@ helm history [flags] RELEASE_NAME
```
--col-width uint specifies the max column width of output (default 60)
--max int32 maximum number of revision to include in history (default 256)
-o, --output string prints the output in the specified format (json|table|yaml) (default "table")
--tls enable TLS for request
--tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem")
--tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem")
@ -40,15 +41,15 @@ helm history [flags] RELEASE_NAME
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 10-Jan-2018
###### Auto generated by spf13/cobra on 14-Mar-2018

@ -17,15 +17,15 @@ helm home
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -43,6 +43,7 @@ helm init
--node-selectors string labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)
-o, --output OutputFormat skip installation and output Tiller's manifest in specified format (json or yaml)
--override stringArray override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)
--replicas int amount of tiller instances to run on the cluster (default 1)
--service-account string name of service account
--skip-refresh do not refresh (download) the local repository cache
--stable-repo-url string URL for stable repository (default "https://kubernetes-charts.storage.googleapis.com")
@ -59,15 +60,15 @@ helm init
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 9-Jan-2018
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -23,7 +23,9 @@ helm inspect [CHART]
--cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password where to locate the requested chart
--repo string chart repository url where to locate the requested chart
--username string chart repository username where to locate the requested chart
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -31,17 +33,18 @@ helm inspect [CHART]
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
* [helm inspect chart](helm_inspect_chart.md) - shows inspect chart
* [helm inspect readme](helm_inspect_readme.md) - shows inspect readme
* [helm inspect values](helm_inspect_values.md) - shows inspect values
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 14-Mar-2018

@ -21,7 +21,9 @@ helm inspect chart [CHART]
--cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password where to locate the requested chart
--repo string chart repository url where to locate the requested chart
--username string chart repository username where to locate the requested chart
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -29,15 +31,15 @@ helm inspect chart [CHART]
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -0,0 +1,43 @@
## helm inspect readme
shows inspect readme
### Synopsis
This command inspects a chart (directory, file, or URL) and displays the contents
of the README file
```
helm inspect readme [CHART]
```
### Options
```
--ca-file string chart repository url where to locate the requested chart
--cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--repo string chart repository url where to locate the requested chart
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 14-Mar-2018

@ -21,7 +21,9 @@ helm inspect values [CHART]
--cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password where to locate the requested chart
--repo string chart repository url where to locate the requested chart
--username string chart repository username where to locate the requested chart
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -29,15 +31,15 @@ helm inspect values [CHART]
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 7-Nov-2017
###### Auto generated by spf13/cobra on 8-Mar-2018

@ -12,7 +12,8 @@ The install argument must be a chart reference, a path to a packaged chart,
a path to an unpacked chart directory or a URL.
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, to force
a string value use '--set-string'.
$ helm install -f myvalues.yaml ./redis
@ -20,6 +21,10 @@ or
$ helm install --set name=prod ./redis
or
$ helm install --set-string long_int=1234567890 ./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:
@ -69,44 +74,48 @@ helm install [CHART]
### Options
```
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file
--dep-up run helm dependency update before installing the chart
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
--dry-run simulate an install
--key-file string identify HTTPS client using this SSL key file
--keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg")
-n, --name string release name. If unspecified, it will autogenerate one for you
--name-template string specify template used to name the release
--namespace string namespace to install the release into. Defaults to the current kube config namespace.
--no-hooks prevent hooks from running during install
--replace re-use the given name, even if that name is already used. This is unsafe in production
--repo string chart repository url where to locate the requested chart
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
--timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300)
--tls enable TLS for request
--tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem")
--tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem")
--tls-key string path to TLS key file (default "$HELM_HOME/key.pem")
--tls-verify enable TLS for request and verify remote
-f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default [])
--verify verify the package before installing it
--version string specify the exact chart version to install. If this is not specified, the latest version is installed
--wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file
--dep-up run helm dependency update before installing the chart
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
--dry-run simulate an install
--key-file string identify HTTPS client using this SSL key file
--keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg")
-n, --name string release name. If unspecified, it will autogenerate one for you
--name-template string specify template used to name the release
--namespace string namespace to install the release into. Defaults to the current kube config namespace.
--no-crd-hook prevent CRD hooks from running, but run other hooks
--no-hooks prevent hooks from running during install
--password string chart repository password where to locate the requested chart
--replace re-use the given name, even if that name is already used. This is unsafe in production
--repo string chart repository url where to locate the requested chart
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
--set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
--timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300)
--tls enable TLS for request
--tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem")
--tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem")
--tls-key string path to TLS key file (default "$HELM_HOME/key.pem")
--tls-verify enable TLS for request and verify remote
--username string chart repository username where to locate the requested chart
-f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default [])
--verify verify the package before installing it
--version string specify the exact chart version to install. If this is not specified, the latest version is installed
--wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout
```
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG
--tiller-namespace string namespace of Tiller (default "kube-system")
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300)
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 23-Nov-2017
###### Auto generated by spf13/cobra on 27-Apr-2018

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

Loading…
Cancel
Save