Merge branch 'master' into feat/add-hpa-and-env

pull/5110/head
Naseem Ullah 7 years ago
commit 56b3e67d2b

1
.gitignore vendored

@ -11,3 +11,4 @@ rootfs/rudder
vendor/ vendor/
*.exe *.exe
.idea/ .idea/
*.iml

@ -84,12 +84,12 @@ your PR will be rejected by the automated DCO check.
Whether you are a user or contributor, official support channels include: Whether you are a user or contributor, official support channels include:
- GitHub [issues](https://github.com/helm/helm/issues/new) - [Issues](https://github.com/helm/helm/issues)
- Slack [Kubernetes Slack](http://slack.kubernetes.io/): - Slack:
- User: #helm-users - User: [#helm-users](https://kubernetes.slack.com/messages/C0NH30761/details/)
- Contributor: #helm-dev - Contributor: [#helm-dev](https://kubernetes.slack.com/messages/C51E88VDG/)
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. 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. It is also worth asking on the Slack channels.
## Milestones ## Milestones
@ -180,33 +180,33 @@ contributing to Helm. All issue types follow the same general lifecycle. Differe
Coding conventions and standards are explained in the official developer docs: Coding conventions and standards are explained in the official developer docs:
[Developers Guide](docs/developers.md) [Developers Guide](docs/developers.md)
The next section contains more information on the workflow followed for PRs The next section contains more information on the workflow followed for Pull Requests.
## Pull Requests ## Pull Requests
Like any good open source project, we use Pull Requests to track code changes Like any good open source project, we use Pull Requests (PRs) to track code changes.
### PR Lifecycle ### PR Lifecycle
1. PR creation 1. PR creation
- PRs are usually created to fix or else be a subset of other PRs that fix a particular issue.
- We more than welcome PRs that are currently in progress. They are a great way to keep track of - We more than welcome PRs that are currently in progress. They are a great way to keep track of
important work that is in-flight, but useful for others to see. If a PR is a work in progress, important work that is in-flight, but useful for others to see. If a PR is a work in progress,
it **must** be prefaced with "WIP: [title]". Once the PR is ready for review, remove "WIP" from it **must** be prefaced with "WIP: [title]". Once the PR is ready for review, remove "WIP" from
the title. the title.
- It is preferred, but not required, to have a PR tied to a specific issue. - It is preferred, but not required, to have a PR tied to a specific issue. There can be
circumstances where if it is a quick fix then an issue might be overkill. The details provided
in the PR description would suffice in this case.
2. Triage 2. Triage
- The maintainer in charge of triaging will apply the proper labels for the issue. This should - The maintainer in charge of triaging will apply the proper labels for the issue. This should
include at least a size label, `bug` or `feature`, and `awaiting review` once all labels are applied. include at least a size label, `bug` or `feature`, and `awaiting review` once all labels are applied.
See the [Labels section](#labels) for full details on the definitions of labels See the [Labels section](#labels) for full details on the definitions of labels.
- Add the PR to the correct milestone. This should be the same as the issue the PR closes. - Add the PR to the correct milestone. This should be the same as the issue the PR closes.
3. Assigning reviews 3. Assigning reviews
- Once a review has the `awaiting review` label, maintainers will review them as schedule permits. - Once a review has the `awaiting review` label, maintainers will review them as schedule permits.
The maintainer who takes the issue should self-request a review. The maintainer who takes the issue should self-request a review.
- Reviews from others in the community, especially those who have encountered a bug or have
requested a feature, are highly encouraged, but not required. Maintainer reviews **are** required
before any merge
- Any PR with the `size/large` label requires 2 review approvals from maintainers before it can be - Any PR with the `size/large` label requires 2 review approvals from maintainers before it can be
merged. Those with `size/medium` are per the judgement of the maintainers merged. Those with `size/medium` or `size/small` are per the judgement of the maintainers.
4. Reviewing/Discussion 4. Reviewing/Discussion
- Once a maintainer begins reviewing a PR, they will remove the `awaiting review` label and add - Once a maintainer begins reviewing a PR, they will remove the `awaiting review` label and add
the `in progress` label so the person submitting knows that it is being worked on. This is the `in progress` label so the person submitting knows that it is being worked on. This is
@ -214,17 +214,24 @@ Like any good open source project, we use Pull Requests to track code changes
- All reviews will be completed using Github review tool. - All reviews will be completed using Github review tool.
- A "Comment" review should be used when there are questions about the code that should be - A "Comment" review should be used when there are questions about the code that should be
answered, but that don't involve code changes. This type of review does not count as approval. answered, but that don't involve code changes. This type of review does not count as approval.
- A "Changes Requested" review indicates that changes to the code need to be made before they will be merged. - A "Changes Requested" review indicates that changes to the code need to be made before they will be
- Reviewers should update labels as needed (such as `needs rebase`) merged.
5. Address comments by answering questions or changing code - Reviewers (maintainers) should update labels as needed (such as `needs rebase`).
- Reviews are also welcome from others in the community, especially those who have encountered a bug or
have requested a feature. In the code review, a message can be added, as well as `LGTM` if the PR is
good to merge. Its also possible to add comments to specific lines in a file, for giving context
to the comment.
5. PR owner should try to be responsive to comments by answering questions or changing code. If the
owner is unsure of any comment, reach out to the person who added the comment in
[#helm-dev](https://kubernetes.slack.com/messages/C51E88VDG/). Once all comments have been addressed,
the PR is ready to be merged.
6. Merge or close 6. Merge or close
- PRs should stay open until merged or if they have not been active for more than 30 days. - PRs should stay open until merged or if they have not been active for more than 30 days.
This will help keep the PR queue to a manageable size and reduce noise. Should the PR need This will help keep the PR queue to a manageable size and reduce noise. Should the PR need
to stay open (like in the case of a WIP), the `keep open` label can be added. to stay open (like in the case of a WIP), the `keep open` label can be added.
- If the owner of the PR is listed in `OWNERS`, that user **must** merge their own PRs - If the owner of the PR is listed in `OWNERS`, that user **must** merge their own PRs or explicitly
or explicitly request another OWNER do that for them. request another OWNER do that for them.
- If the owner of a PR is _not_ listed in `OWNERS`, any core committer may - If the owner of a PR is _not_ listed in `OWNERS`, any maintainer may merge the PR once it is approved.
merge the PR once it is approved.
#### Documentation PRs #### Documentation PRs

@ -32,6 +32,7 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install ## Install
Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest). Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest).
Unpack the `helm` binary and add it to your PATH and you are good to go! Unpack the `helm` binary and add it to your PATH and you are good to go!
@ -40,6 +41,7 @@ If you want to use a package manager:
- [Homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`. - [Homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`.
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [GoFish](https://gofi.sh/) users can use `gofish install helm`. - [GoFish](https://gofi.sh/) users can use `gofish install helm`.
To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide). To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide).

@ -76,7 +76,7 @@ service ReleaseService {
rpc RollbackRelease(RollbackReleaseRequest) returns (RollbackReleaseResponse) { rpc RollbackRelease(RollbackReleaseRequest) returns (RollbackReleaseResponse) {
} }
// ReleaseHistory retrieves a releasse's history. // ReleaseHistory retrieves a release's history.
rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) { rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) {
} }
@ -212,6 +212,8 @@ message UpdateReleaseRequest {
bool force = 11; bool force = 11;
// Description, if set, will set the description for the updated release // Description, if set, will set the description for the updated release
string description = 12; string description = 12;
// Render subchart notes if enabled
bool subNotes = 13;
} }
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.
@ -281,6 +283,9 @@ message InstallReleaseRequest {
// Description, if set, will set the description for the installed release // Description, if set, will set the description for the installed release
string description = 11; string description = 11;
bool subNotes = 12;
} }
// InstallReleaseResponse is the response from a release installation. // InstallReleaseResponse is the response from a release installation.
@ -298,7 +303,7 @@ message UninstallReleaseRequest {
bool purge = 3; bool purge = 3;
// timeout specifies the max amount of time any kubernetes client command can run. // timeout specifies the max amount of time any kubernetes client command can run.
int64 timeout = 4; int64 timeout = 4;
// Description, if set, will set the description for the uninnstalled release // Description, if set, will set the description for the uninstalled release
string description = 5; string description = 5;
} }

@ -212,6 +212,7 @@ __helm_convert_bash_to_zsh() {
-e "s/${LWORD}compopt${RWORD}/__helm_compopt/g" \ -e "s/${LWORD}compopt${RWORD}/__helm_compopt/g" \
-e "s/${LWORD}declare${RWORD}/__helm_declare/g" \ -e "s/${LWORD}declare${RWORD}/__helm_declare/g" \
-e "s/\\\$(type${RWORD}/\$(__helm_type/g" \ -e "s/\\\$(type${RWORD}/\$(__helm_type/g" \
-e 's/aliashash\["\(\w\+\)"\]/aliashash[\1]/g' \
<<'BASH_COMPLETION_EOF' <<'BASH_COMPLETION_EOF'
` `
out.Write([]byte(zshInitialization)) out.Write([]byte(zshInitialization))

@ -36,6 +36,7 @@ import (
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/helm/portforwarder" "k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/version"
) )
const initDesc = ` const initDesc = `
@ -315,6 +316,14 @@ func (i *initCmd) run() error {
fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set") fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set")
} }
needsDefaultImage := !i.clientOnly && !i.opts.UseCanary && len(i.opts.ImageSpec) == 0 && version.BuildMetadata == "unreleased"
if needsDefaultImage {
fmt.Fprintf(i.out, "\nWarning: You appear to be using an unreleased version of Helm. Please either use the\n"+
"--canary-image flag, or specify your desired tiller version with --tiller-image.\n\n"+
"Ex:\n"+
"$ helm init --tiller-image gcr.io/kubernetes-helm/tiller:v2.8.2\n\n")
}
fmt.Fprintln(i.out, "Happy Helming!") fmt.Fprintln(i.out, "Happy Helming!")
return nil return nil
} }

@ -59,6 +59,7 @@ type inspectCmd struct {
repoURL string repoURL string
username string username string
password string password string
devel bool
certFile string certFile string
keyFile string keyFile string
@ -88,12 +89,9 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil { if err := checkArgsLength(len(args), "chart name"); err != nil {
return err return err
} }
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, if err := insp.prepare(args[0]); err != nil {
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err return err
} }
insp.chartpath = cp
return insp.run() return insp.run()
}, },
} }
@ -107,12 +105,9 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil { if err := checkArgsLength(len(args), "chart name"); err != nil {
return err return err
} }
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, if err := insp.prepare(args[0]); err != nil {
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err return err
} }
insp.chartpath = cp
return insp.run() return insp.run()
}, },
} }
@ -126,12 +121,9 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil { if err := checkArgsLength(len(args), "chart name"); err != nil {
return err return err
} }
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, if err := insp.prepare(args[0]); err != nil {
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err return err
} }
insp.chartpath = cp
return insp.run() return insp.run()
}, },
} }
@ -145,12 +137,9 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil { if err := checkArgsLength(len(args), "chart name"); err != nil {
return err return err
} }
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, if err := insp.prepare(args[0]); err != nil {
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
return err return err
} }
insp.chartpath = cp
return insp.run() return insp.run()
}, },
} }
@ -193,6 +182,12 @@ func newInspectCmd(out io.Writer) *cobra.Command {
valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc) valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc) chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
develFlag := "devel"
develDesc := "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored."
for _, subCmd := range cmds {
subCmd.Flags().BoolVar(&insp.devel, develFlag, false, develDesc)
}
certFile := "cert-file" certFile := "cert-file"
certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle" certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle"
for _, subCmd := range cmds { for _, subCmd := range cmds {
@ -218,6 +213,22 @@ func newInspectCmd(out io.Writer) *cobra.Command {
return inspectCommand return inspectCommand
} }
func (i *inspectCmd) prepare(chart string) error {
debug("Original chart version: %q", i.version)
if i.version == "" && i.devel {
debug("setting version to >0.0.0-0")
i.version = ">0.0.0-0"
}
cp, err := locateChartPath(i.repoURL, i.username, i.password, chart, i.version, i.verify, i.keyring,
i.certFile, i.keyFile, i.caFile)
if err != nil {
return err
}
i.chartpath = cp
return nil
}
func (i *inspectCmd) run() error { func (i *inspectCmd) run() error {
chrt, err := chartutil.Load(i.chartpath) chrt, err := chartutil.Load(i.chartpath)
if err != nil { if err != nil {

@ -19,8 +19,11 @@ package main
import ( import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"testing" "testing"
"k8s.io/helm/pkg/repo/repotest"
) )
func TestInspect(t *testing.T) { func TestInspect(t *testing.T) {
@ -78,3 +81,66 @@ func TestInspect(t *testing.T) {
t.Errorf("expected empty values buffer, got %q", b.String()) t.Errorf("expected empty values buffer, got %q", b.String())
} }
} }
func TestInspectPreReleaseChart(t *testing.T) {
hh, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
os.RemoveAll(hh.String())
cleanup()
}()
settings.Home = hh
srv := repotest.NewServer(hh.String())
defer srv.Stop()
if _, err := srv.CopyCharts("testdata/testcharts/*.tgz*"); err != nil {
t.Fatal(err)
}
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
args []string
flags []string
fail bool
expectedErr string
}{
{
name: "inspect pre-release chart",
args: []string{"prerelease"},
fail: true,
expectedErr: "chart \"prerelease\" not found",
},
{
name: "inspect pre-release chart with 'devel' flag",
args: []string{"prerelease"},
flags: []string{"--devel"},
fail: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.flags = append(tt.flags, "--repo", srv.URL())
cmd := newInspectCmd(ioutil.Discard)
cmd.SetArgs(tt.args)
cmd.ParseFlags(tt.flags)
if err := cmd.RunE(cmd, tt.args); err != nil {
if tt.fail {
if !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf("%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error())
}
return
}
t.Errorf("%q reported error: %s", tt.name, err)
}
})
}
}

@ -131,11 +131,13 @@ type installCmd struct {
version string version string
timeout int64 timeout int64
wait bool wait bool
atomic bool
repoURL string repoURL string
username string username string
password string password string
devel bool devel bool
depUp bool depUp bool
subNotes bool
description string description string
certFile string certFile string
@ -189,6 +191,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
inst.chartPath = cp inst.chartPath = cp
inst.client = ensureHelmClient(inst.client) inst.client = ensureHelmClient(inst.client)
inst.wait = inst.wait || inst.atomic
return inst.run() return inst.run()
}, },
} }
@ -211,6 +215,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") 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.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.BoolVar(&inst.atomic, "atomic", false, "if set, installation process purges chart on fail, also sets --wait flag")
f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart") 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.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.password, "password", "", "chart repository password where to locate the requested chart")
@ -219,6 +224,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart") f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart")
f.BoolVar(&inst.subNotes, "render-subchart-notes", false, "render subchart notes along with the parent")
f.StringVar(&inst.description, "description", "", "specify a description for the release") f.StringVar(&inst.description, "description", "", "specify a description for the release")
// set defaults from environment // set defaults from environment
@ -249,8 +255,8 @@ func (i *installCmd) run() error {
fmt.Printf("FINAL NAME: %s\n", i.name) fmt.Printf("FINAL NAME: %s\n", i.name)
} }
if msgs := validation.IsDNS1123Label(i.name); i.name != "" && len(msgs) > 0 { if msgs := validation.IsDNS1123Subdomain(i.name); i.name != "" && len(msgs) > 0 {
return fmt.Errorf("release name %s is not a valid DNS label: %s", i.name, strings.Join(msgs, ";")) return fmt.Errorf("release name %s is invalid: %s", i.name, strings.Join(msgs, ";"))
} }
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts
@ -300,10 +306,28 @@ func (i *installCmd) run() error {
helm.InstallReuseName(i.replace), helm.InstallReuseName(i.replace),
helm.InstallDisableHooks(i.disableHooks), helm.InstallDisableHooks(i.disableHooks),
helm.InstallDisableCRDHook(i.disableCRDHook), helm.InstallDisableCRDHook(i.disableCRDHook),
helm.InstallSubNotes(i.subNotes),
helm.InstallTimeout(i.timeout), helm.InstallTimeout(i.timeout),
helm.InstallWait(i.wait), helm.InstallWait(i.wait),
helm.InstallDescription(i.description)) helm.InstallDescription(i.description))
if err != nil { if err != nil {
if i.atomic {
fmt.Fprintf(os.Stdout, "INSTALL FAILED\nPURGING CHART\nError: %v\n", prettyError(err))
deleteSideEffects := &deleteCmd{
name: i.name,
disableHooks: i.disableHooks,
purge: true,
timeout: i.timeout,
description: "",
dryRun: i.dryRun,
out: i.out,
client: i.client,
}
if err := deleteSideEffects.run(); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Successfully purged a chart!\n")
}
return prettyError(err) return prettyError(err)
} }

@ -113,6 +113,14 @@ func TestInstall(t *testing.T) {
expected: "apollo", expected: "apollo",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}), resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}),
}, },
// Install, with atomic
{
name: "install with a atomic",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name apollo", " "),
expected: "apollo",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}),
},
// Install, using the name-template // Install, using the name-template
{ {
name: "install with name-template", name: "install with name-template",
@ -169,7 +177,6 @@ func TestInstall(t *testing.T) {
name: "install chart with release name using periods", name: "install chart with release name using periods",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
flags: []string{"--name", "foo.bar"}, flags: []string{"--name", "foo.bar"},
err: true,
}, },
{ {
name: "install chart with release name using underscores", name: "install chart with release name using underscores",

@ -183,7 +183,7 @@ func generateLabels(labels map[string]string) map[string]string {
return labels return labels
} }
// parseNodeSelectors parses a comma delimited list of key=values pairs into a map. // parseNodeSelectorsInto parses a comma delimited list of key=values pairs into a map.
func parseNodeSelectorsInto(labels string, m map[string]string) error { func parseNodeSelectorsInto(labels string, m map[string]string) error {
kv := strings.Split(labels, ",") kv := strings.Split(labels, ",")
for _, v := range kv { for _, v := range kv {

@ -53,6 +53,10 @@ func TestDeployment(t *testing.T) {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
// Unreleased versions of helm don't have a release image. See issue 3370
if tt.name == "default" && version.BuildMetadata == "unreleased" {
tt.expect = "gcr.io/kubernetes-helm/tiller:canary"
}
if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect { if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect {
t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got) t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got)
} }

@ -50,7 +50,7 @@ type Options struct {
// AutoMountServiceAccountToken determines whether or not the service account should be added to Tiller. // AutoMountServiceAccountToken determines whether or not the service account should be added to Tiller.
AutoMountServiceAccountToken bool AutoMountServiceAccountToken bool
// Force allows to force upgrading tiller if deployed version is greater than current version // ForceUpgrade allows to force upgrading tiller if deployed version is greater than current version
ForceUpgrade bool ForceUpgrade bool
// ImageSpec identifies the image Tiller will use when deployed. // ImageSpec identifies the image Tiller will use when deployed.
@ -105,6 +105,9 @@ func (opts *Options) SelectImage() string {
case opts.UseCanary: case opts.UseCanary:
return defaultImage + ":canary" return defaultImage + ":canary"
case opts.ImageSpec == "": case opts.ImageSpec == "":
if version.BuildMetadata == "unreleased" {
return defaultImage + ":canary"
}
return fmt.Sprintf("%s:%s", defaultImage, version.Version) return fmt.Sprintf("%s:%s", defaultImage, version.Version)
default: default:
return opts.ImageSpec return opts.ImageSpec

@ -47,10 +47,11 @@ func deleteService(client corev1.ServicesGetter, namespace string) error {
} }
// deleteDeployment deletes the Tiller Deployment resource // deleteDeployment deletes the Tiller Deployment resource
// We need to use the reaper instead of the kube API because GC for deployment dependents
// is not yet supported at the k8s server level (<= 1.5)
func deleteDeployment(client kubernetes.Interface, namespace string) error { func deleteDeployment(client kubernetes.Interface, namespace string) error {
err := client.Extensions().Deployments(namespace).Delete(deploymentName, &metav1.DeleteOptions{}) policy := metav1.DeletePropagationBackground
err := client.AppsV1().Deployments(namespace).Delete(deploymentName, &metav1.DeleteOptions{
PropagationPolicy: &policy,
})
return ingoreNotFound(err) return ingoreNotFound(err)
} }

@ -93,21 +93,28 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer, home helmpath.Ho
var ( var (
errorCounter int errorCounter int
wg sync.WaitGroup wg sync.WaitGroup
mu sync.Mutex
) )
for _, re := range repos { for _, re := range repos {
wg.Add(1) wg.Add(1)
go func(re *repo.ChartRepository) { go func(re *repo.ChartRepository) {
defer wg.Done() defer wg.Done()
if re.Config.Name == localRepository { if re.Config.Name == localRepository {
mu.Lock()
fmt.Fprintf(out, "...Skip %s chart repository\n", re.Config.Name) fmt.Fprintf(out, "...Skip %s chart repository\n", re.Config.Name)
mu.Unlock()
return return
} }
err := re.DownloadIndexFile(home.Cache()) err := re.DownloadIndexFile(home.Cache())
if err != nil { if err != nil {
mu.Lock()
errorCounter++ errorCounter++
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err) fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
mu.Unlock()
} else { } else {
mu.Lock()
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)
mu.Unlock()
} }
}(re) }(re)
} }

@ -105,3 +105,30 @@ func TestUpdateCharts(t *testing.T) {
t.Error("Update was not successful") t.Error("Update was not successful")
} }
} }
func TestUpdateCmdStrictFlag(t *testing.T) {
thome, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
os.RemoveAll(thome.String())
cleanup()
}()
settings.Home = thome
out := bytes.NewBuffer(nil)
cmd := newRepoUpdateCmd(out)
cmd.ParseFlags([]string{"--strict"})
if err := cmd.RunE(cmd, []string{}); err == nil {
t.Fatal("expected error due to strict flag")
}
if got := out.String(); !strings.Contains(got, "Unable to get an update") {
t.Errorf("Expected 'Unable to get an update', got %q", got)
}
}

@ -31,7 +31,8 @@ This command rolls back a release to a previous revision.
The first argument of the rollback command is the name of a release, and the The first argument of the rollback command is the name of a release, and the
second is a revision (version) number. To see revision numbers, run second is a revision (version) number. To see revision numbers, run
'helm history RELEASE'. 'helm history RELEASE'. If you'd like to rollback to the previous release use
'helm rollback [RELEASE] 0'.
` `
type rollbackCmd struct { type rollbackCmd struct {

@ -154,7 +154,7 @@ func (s *searchCmd) buildIndex() (*search.Index, error) {
f := s.helmhome.CacheIndex(n) f := s.helmhome.CacheIndex(n)
ind, err := repo.LoadIndexFile(f) ind, err := repo.LoadIndexFile(f)
if err != nil { if err != nil {
fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n) fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.\n", n)
continue continue
} }

@ -147,8 +147,8 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
} }
} }
if msgs := validation.IsDNS1123Label(t.releaseName); t.releaseName != "" && len(msgs) > 0 { if msgs := validation.IsDNS1123Subdomain(t.releaseName); t.releaseName != "" && len(msgs) > 0 {
return fmt.Errorf("release name %s is not a valid DNS label: %s", t.releaseName, strings.Join(msgs, ";")) return fmt.Errorf("release name %s is invalid: %s", t.releaseName, strings.Join(msgs, ";"))
} }
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts

@ -112,21 +112,21 @@ func TestTemplateCmd(t *testing.T) {
desc: "verify the release name using capitals is invalid", desc: "verify the release name using capitals is invalid",
args: []string{subchart1ChartPath, "--name", "FOO"}, args: []string{subchart1ChartPath, "--name", "FOO"},
expectKey: "subchart1/templates/service.yaml", expectKey: "subchart1/templates/service.yaml",
expectError: "is not a valid DNS label", expectError: "is invalid",
}, },
{ {
name: "check_invalid_name_uppercase", name: "check_invalid_name_uppercase",
desc: "verify the release name using periods is invalid", desc: "verify the release name using periods is invalid",
args: []string{subchart1ChartPath, "--name", "foo.bar"}, args: []string{subchart1ChartPath, "--name", "foo.bar"},
expectKey: "subchart1/templates/service.yaml", expectKey: "subchart1/templates/service.yaml",
expectError: "is not a valid DNS label", expectValue: "release-name: \"foo.bar\"",
}, },
{ {
name: "check_invalid_name_uppercase", name: "check_invalid_name_uppercase",
desc: "verify the release name using underscores is invalid", desc: "verify the release name using underscores is invalid",
args: []string{subchart1ChartPath, "--name", "foo_bar"}, args: []string{subchart1ChartPath, "--name", "foo_bar"},
expectKey: "subchart1/templates/service.yaml", expectKey: "subchart1/templates/service.yaml",
expectError: "is not a valid DNS label", expectError: "is invalid",
}, },
{ {
name: "check_release_is_install", name: "check_release_is_install",
@ -160,7 +160,7 @@ func TestTemplateCmd(t *testing.T) {
name: "check_invalid_name_template", name: "check_invalid_name_template",
desc: "verify the relase name generate by template is invalid", desc: "verify the relase name generate by template is invalid",
args: []string{subchart1ChartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"}, args: []string{subchart1ChartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"},
expectError: "is not a valid DNS label", expectError: "is invalid",
}, },
{ {
name: "check_name_template", name: "check_name_template",

@ -0,0 +1,6 @@
description: Deploy a basic Alpine Linux pod
home: https://k8s.io/helm
name: prerelease
sources:
- https://github.com/helm/helm
version: 0.2.0-pre-release

@ -0,0 +1,13 @@
#Alpine: A simple Helm chart
Run a single pod of Alpine Linux.
This example was generated using the command `helm create alpine`.
The `templates/` directory contains a very simple pod resource with a
couple of parameters.
The `values.yaml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install docs/examples/alpine`.

@ -0,0 +1,26 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-{{.Values.Name}}"
labels:
# The "heritage" label is used to track which tool deployed a given chart.
# It is useful for admins who want to see what releases a particular tool
# is responsible for.
app.kubernetes.io/managed-by: {{.Release.Service | quote }}
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
app.kubernetes.io/instance: {{.Release.Name | quote }}
# This makes it easy to audit chart usage.
helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:
"helm.sh/created": {{.Release.Time.Seconds | quote }}
spec:
# This shows how to use a simple value. This will look for a passed-in value
# called restartPolicy. If it is not found, it will use the default value.
# {{default "Never" .restartPolicy}} is a slightly optimized version of the
# more conventional syntax: {{.restartPolicy | default "Never"}}
restartPolicy: {{default "Never" .Values.restartPolicy}}
containers:
- name: waiter
image: "alpine:3.3"
command: ["/bin/sleep","9000"]

@ -44,7 +44,7 @@ To customize the chart values, use any of
- '--set-string' to provide key=val forcing val to be stored as a string, - '--set-string' to provide key=val forcing val to be stored as a string,
- '--set-file' to provide key=path to read a single large value from a file at path. - '--set-file' to provide key=path to read a single large value from a file at path.
To edit or append to the existing customized values, add the To edit or append to the existing customized values, add the
'--reuse-values' flag, otherwise any existing customized values are ignored. '--reuse-values' flag, otherwise any existing customized values are ignored.
If no chart value arguments are provided on the command line, any existing customized values are carried If no chart value arguments are provided on the command line, any existing customized values are carried
@ -105,10 +105,12 @@ type upgradeCmd struct {
resetValues bool resetValues bool
reuseValues bool reuseValues bool
wait bool wait bool
atomic bool
repoURL string repoURL string
username string username string
password string password string
devel bool devel bool
subNotes bool
description string description string
certFile string certFile string
@ -141,6 +143,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
upgrade.release = args[0] upgrade.release = args[0]
upgrade.chart = args[1] upgrade.chart = args[1]
upgrade.client = ensureHelmClient(upgrade.client) upgrade.client = ensureHelmClient(upgrade.client)
upgrade.wait = upgrade.wait || upgrade.atomic
return upgrade.run() return upgrade.run()
}, },
@ -166,6 +169,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") 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 overrides from the command line via --set and -f. 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.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.BoolVar(&upgrade.atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade, also sets --wait flag")
f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart") 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.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.password, "password", "", "chart repository password where to locate the requested chart")
@ -173,6 +177,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key 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") f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&upgrade.subNotes, "render-subchart-notes", false, "render subchart notes along with parent")
f.StringVar(&upgrade.description, "description", "", "specify the description to use for the upgrade, rather than the default") f.StringVar(&upgrade.description, "description", "", "specify the description to use for the upgrade, rather than the default")
f.MarkDeprecated("disable-hooks", "use --no-hooks instead") f.MarkDeprecated("disable-hooks", "use --no-hooks instead")
@ -189,6 +194,8 @@ func (u *upgradeCmd) run() error {
return err return err
} }
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if u.install { if u.install {
// If a release does not exist, install it. If another error occurs during // If a release does not exist, install it. If another error occurs during
// the check, ignore the error and continue with the upgrade. // the check, ignore the error and continue with the upgrade.
@ -196,7 +203,6 @@ func (u *upgradeCmd) run() error {
// The returned error is a grpc.rpcError that wraps the message from the original error. // The returned error is a grpc.rpcError that wraps the message from the original error.
// So we're stuck doing string matching against the wrapped error, which is nested somewhere // So we're stuck doing string matching against the wrapped error, which is nested somewhere
// inside of the grpc.rpcError message. // inside of the grpc.rpcError message.
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if err == nil { if err == nil {
if u.namespace == "" { if u.namespace == "" {
@ -230,6 +236,7 @@ func (u *upgradeCmd) run() error {
timeout: u.timeout, timeout: u.timeout,
wait: u.wait, wait: u.wait,
description: u.description, description: u.description,
atomic: u.atomic,
} }
return ic.run() return ic.run()
} }
@ -264,9 +271,29 @@ func (u *upgradeCmd) run() error {
helm.UpgradeTimeout(u.timeout), helm.UpgradeTimeout(u.timeout),
helm.ResetValues(u.resetValues), helm.ResetValues(u.resetValues),
helm.ReuseValues(u.reuseValues), helm.ReuseValues(u.reuseValues),
helm.UpgradeSubNotes(u.subNotes),
helm.UpgradeWait(u.wait), helm.UpgradeWait(u.wait),
helm.UpgradeDescription(u.description)) helm.UpgradeDescription(u.description))
if err != nil { if err != nil {
fmt.Fprintf(u.out, "UPGRADE FAILED\nROLLING BACK\nError: %v\n", prettyError(err))
if u.atomic {
rollback := &rollbackCmd{
out: u.out,
client: u.client,
name: u.release,
dryRun: u.dryRun,
recreate: u.recreate,
force: u.force,
timeout: u.timeout,
wait: u.wait,
description: "",
revision: releaseHistory.Releases[0].Version,
disableHooks: u.disableHooks,
}
if err := rollback.run(); err != nil {
return err
}
}
return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err))
} }

@ -123,6 +123,14 @@ func TestUpgradeCmd(t *testing.T) {
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2})}, rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2})},
}, },
{
name: "install a release with 'upgrade --atomic'",
args: []string{"funny-bunny", chartPath},
flags: []string{"--atomic"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 6, Chart: ch}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 6, Chart: ch})},
},
{ {
name: "install a release with 'upgrade --install'", name: "install a release with 'upgrade --install'",
args: []string{"zany-bunny", chartPath}, args: []string{"zany-bunny", chartPath},

@ -28,10 +28,10 @@ resources that use that CRD in _another_ chart.
In this method, each chart must be installed separately. In this method, each chart must be installed separately.
### Method 2: Pre-install Hooks ### Method 2: Crd-install Hooks
To package the two together, add a `pre-install` hook to the CRD definition so To package the two together, add a `crd-install` hook to the CRD definition so
that it is fully installed before the rest of the chart is executed. 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 Note that if you create the CRD with a `crd-install` hook, that CRD definition
will not be deleted when `helm delete` is run. will not be deleted when `helm delete` is run.

@ -20,7 +20,7 @@ The first control structure we'll look at is for conditionally including blocks
The basic structure for a conditional looks like this: The basic structure for a conditional looks like this:
``` ```yaml
{{ if PIPELINE }} {{ if PIPELINE }}
# Do something # Do something
{{ else if OTHER PIPELINE }} {{ else if OTHER PIPELINE }}
@ -53,7 +53,7 @@ data:
myvalue: "Hello World" myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }} drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }} food: {{ .Values.favorite.food | upper | quote }}
{{ if and (.Values.favorite.drink) (eq .Values.favorite.drink "coffee") }}mug: true{{ end }} {{ if and .Values.favorite.drink (eq .Values.favorite.drink "coffee") }}mug: true{{ end }}
``` ```
Note that `.Values.favorite.drink` must be defined or else it will throw an error when comparing it to "coffee". Since we commented out `drink: coffee` in our last example, the output should not include a `mug: true` flag. But if we add that line back into our `values.yaml` file, the output should look like this: Note that `.Values.favorite.drink` must be defined or else it will throw an error when comparing it to "coffee". Since we commented out `drink: coffee` in our last example, the output should not include a `mug: true` flag. But if we add that line back into our `values.yaml` file, the output should look like this:
@ -115,7 +115,7 @@ data:
`mug` is incorrectly indented. Let's simply out-dent that one line, and re-run: `mug` is incorrectly indented. Let's simply out-dent that one line, and re-run:
``` ```yaml
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
@ -224,7 +224,7 @@ The next control structure to look at is the `with` action. This controls variab
The syntax for `with` is similar to a simple `if` statement: The syntax for `with` is similar to a simple `if` statement:
``` ```yaml
{{ with PIPELINE }} {{ with PIPELINE }}
# restricted scope # restricted scope
{{ end }} {{ end }}

@ -12,7 +12,7 @@ When your YAML is failing to parse, but you want to see what is generated, one
easy way to retrieve the YAML is to comment out the problem section in the template, easy way to retrieve the YAML is to comment out the problem section in the template,
and then re-run `helm install --dry-run --debug`: and then re-run `helm install --dry-run --debug`:
```YAML ```yaml
apiVersion: v1 apiVersion: v1
# some: problem section # some: problem section
# {{ .Values.foo | quote }} # {{ .Values.foo | quote }}
@ -20,7 +20,7 @@ apiVersion: v1
The above will be rendered and returned with the comments intact: The above will be rendered and returned with the comments intact:
```YAML ```yaml
apiVersion: v1 apiVersion: v1
# some: problem section # some: problem section
# "bar" # "bar"

@ -4,7 +4,7 @@ So far, we've seen how to place information into a template. But that informatio
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: 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:
``` ```yaml
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
@ -104,7 +104,7 @@ drink: {{ .Values.favorite.drink | default "tea" | quote }}
If we run this as normal, we'll get our `coffee`: If we run this as normal, we'll get our `coffee`:
``` ```yaml
# Source: mychart/templates/configmap.yaml # Source: mychart/templates/configmap.yaml
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
@ -150,6 +150,19 @@ Template functions and pipelines are a powerful way to transform information and
## Operators are functions ## Operators are functions
For templates, the operators (`eq`, `ne`, `lt`, `gt`, `and`, `or` and so on) are all implemented as functions. In pipelines, operations can be grouped with parentheses (`(`, and `)`). Operators are implemented as functions that return a boolean value. To use `eq`, `ne`, `lt`, `gt`, `and`, `or`, `not` etcetera place the operator at the front of the statement followed by its parameters just as you would a function. To chain multiple operations together, separate individual functions by surrounding them with paranthesis.
```yaml
{{/* include the body of this if statement when the variable .Values.fooString exists and is set to "foo" */}}
{{ if and .Values.fooString (eq .Values.fooString "foo") }}
{{ ... }}
{{ end }}
{{/* do not include the body of this if statement because unset variables evaluate to false and .Values.setVariable was negated with the not function. */}}
{{ if or .Values.anUnsetVariable (not .Values.aSetVariable) }}
{{ ... }}
{{ end }}
```
Now we can turn from functions and pipelines to flow control with conditions, loops, and scope modifiers. Now we can turn from functions and pipelines to flow control with conditions, loops, and scope modifiers.

@ -63,7 +63,7 @@ data:
dessert: cake dessert: cake
``` ```
## Overriding Values from a Parent Chart ## Overriding Values of a Child Chart
Our original chart, `mychart` is now the _parent_ chart of `mysubchart`. This relationship is based entirely on the fact that `mysubchart` is within `mychart/charts`. Our original chart, `mychart` is now the _parent_ chart of `mysubchart`. This relationship is based entirely on the fact that `mysubchart` is within `mychart/charts`.

@ -54,7 +54,7 @@ data:
Because `favoriteDrink` is set in the default `values.yaml` file to `coffee`, that's the value displayed in the template. We can easily override that by adding a `--set` flag in our call to `helm install`: Because `favoriteDrink` is set in the default `values.yaml` file to `coffee`, that's the value displayed in the template. We can easily override that by adding a `--set` flag in our call to `helm install`:
``` ```console
helm install --dry-run --debug --set favoriteDrink=slurm ./mychart helm install --dry-run --debug --set favoriteDrink=slurm ./mychart
SERVER: "localhost:44134" SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
@ -85,7 +85,7 @@ favorite:
Now we would have to modify the template slightly: Now we would have to modify the template slightly:
``` ```yaml
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:

@ -98,10 +98,7 @@ data:
Variables are normally not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block. Variables are normally not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block.
However, there is one variable that is always global - `$` - this However, there is one variable that is always global - `$` - this variable will always point to the root context. This can be very useful when you are looping in a range and need to know the chart's release name.
variable will always point to the root context. This can be very
useful when you are looping in a range need to know the chart's release
name.
An example illustrating this: An example illustrating this:
```yaml ```yaml
@ -111,8 +108,8 @@ kind: Secret
metadata: metadata:
name: {{ .name }} name: {{ .name }}
labels: labels:
# Many helm templates would use `.` below, but that will not work, # Many helm templates would use `.` below, but that will not work,
# however `$` will work here # however `$` will work here
app.kubernetes.io/name: {{ template "fullname" $ }} app.kubernetes.io/name: {{ template "fullname" $ }}
# I cannot reference .Chart.Name, but I can do $.Chart.Name # I cannot reference .Chart.Name, but I can do $.Chart.Name
helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}" helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"

@ -177,7 +177,7 @@ Now the value of `coffee` will be `Latte\nCappuccino\nEspresso\n\n\n`.
Indentation inside of a text block is preserved, and results in the preservation Indentation inside of a text block is preserved, and results in the preservation
of line breaks, too: of line breaks, too:
``` ```yaml
coffee: |- coffee: |-
Latte Latte
12 oz 12 oz
@ -336,7 +336,7 @@ reference is expanded and then discarded.
So if we were to decode and then re-encode the example above, the resulting So if we were to decode and then re-encode the example above, the resulting
YAML would be: YAML would be:
```YAML ```yaml
coffee: yes, please coffee: yes, please
favorite: Cappucino favorite: Cappucino
coffees: coffees:

@ -64,7 +64,7 @@ spec:
``` ```
## Steps to Run a Test Suite on a Release ## Steps to Run a Test Suite on a Release
1. `$ helm install wordpress` 1. `$ helm install stable/wordpress`
``` ```
NAME: quirky-walrus NAME: quirky-walrus
LAST DEPLOYED: Mon Feb 13 13:50:43 2017 LAST DEPLOYED: Mon Feb 13 13:50:43 2017

@ -36,6 +36,12 @@ is required, and will print an error message when that entry is missing:
value: {{ required "A valid .Values.who entry required!" .Values.who }} value: {{ required "A valid .Values.who entry required!" .Values.who }}
``` ```
When using the `include` function, you can pass it a custom object tree built from the current context by using the `dict` function:
```yaml
{{- include "mytpl" (dict "key1" .Values.originalKey1 "key2" .Values.originalKey2) }}
```
## Quote Strings, Don't Quote Integers ## Quote Strings, Don't Quote Integers
When you are working with string data, you are always safer quoting the When you are working with string data, you are always safer quoting the
@ -255,9 +261,9 @@ embed each of the components.
Two strong design patterns are illustrated by these projects: Two strong design patterns are illustrated by these projects:
**SAP's [OpenStack chart](https://github.com/sapcc/openstack-helm):** This chart **SAP's [Converged charts](https://github.com/sapcc/helm-charts):** These charts
installs a full OpenStack IaaS on Kubernetes. All of the charts are collected install SAP Converged Cloud a full OpenStack IaaS on Kubernetes. All of the charts are collected
together in one GitHub repository. together in one GitHub repository, except for a few submodules.
**Deis's [Workflow](https://github.com/deis/workflow/tree/master/charts/workflow):** **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 This chart exposes the entire Deis PaaS system with one chart. But it's different

@ -1,6 +1,6 @@
image: image:
repository: alpine repository: alpine
tag: 3.3 tag: latest
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
restartPolicy: Never restartPolicy: Never

@ -32,6 +32,6 @@ spec:
restartPolicy: {{ .Values.restartPolicy }} restartPolicy: {{ .Values.restartPolicy }}
containers: containers:
- name: post-install-job - name: post-install-job
image: "alpine:3.3" image: "alpine:latest"
# All we're going to do is sleep for a while, then exit. # All we're going to do is sleep for a while, then exit.
command: ["/bin/sleep", "{{ .Values.sleepyTime }}"] command: ["/bin/sleep", "{{ .Values.sleepyTime }}"]

@ -14,7 +14,7 @@ index: >-
image: image:
repository: nginx repository: nginx
tag: 1.11.0 tag: alpine
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
service: service:

@ -20,6 +20,7 @@ helm inspect [CHART] [flags]
``` ```
--ca-file string chart repository url where to locate the requested chart --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 --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
-h, --help help for inspect -h, --help help for inspect
--key-file string identify HTTPS client using this SSL key file --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") --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
@ -49,4 +50,4 @@ helm inspect [CHART] [flags]
* [helm inspect readme](helm_inspect_readme.md) - shows inspect readme * [helm inspect readme](helm_inspect_readme.md) - shows inspect readme
* [helm inspect values](helm_inspect_values.md) - shows inspect values * [helm inspect values](helm_inspect_values.md) - shows inspect values
###### Auto generated by spf13/cobra on 1-Aug-2018 ###### Auto generated by spf13/cobra on 8-Jan-2019

@ -18,6 +18,7 @@ helm inspect chart [CHART] [flags]
``` ```
--ca-file string chart repository url where to locate the requested chart --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 --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
-h, --help help for chart -h, --help help for chart
--key-file string identify HTTPS client using this SSL key file --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") --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
@ -44,4 +45,4 @@ helm inspect chart [CHART] [flags]
* [helm inspect](helm_inspect.md) - inspect a chart * [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 1-Aug-2018 ###### Auto generated by spf13/cobra on 8-Jan-2019

@ -18,6 +18,7 @@ helm inspect readme [CHART] [flags]
``` ```
--ca-file string chart repository url where to locate the requested chart --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 --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
-h, --help help for readme -h, --help help for readme
--key-file string identify HTTPS client using this SSL key file --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") --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
@ -42,4 +43,4 @@ helm inspect readme [CHART] [flags]
* [helm inspect](helm_inspect.md) - inspect a chart * [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 1-Aug-2018 ###### Auto generated by spf13/cobra on 8-Jan-2019

@ -18,6 +18,7 @@ helm inspect values [CHART] [flags]
``` ```
--ca-file string chart repository url where to locate the requested chart --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 --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle
--devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
-h, --help help for values -h, --help help for values
--key-file string identify HTTPS client using this SSL key file --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") --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
@ -44,4 +45,4 @@ helm inspect values [CHART] [flags]
* [helm inspect](helm_inspect.md) - inspect a chart * [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 1-Aug-2018 ###### Auto generated by spf13/cobra on 8-Jan-2019

@ -78,6 +78,7 @@ helm install [CHART] [flags]
### Options ### Options
``` ```
--atomic if set, installation process purges chart on fail, also sets --wait flag
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file --cert-file string identify HTTPS client using this SSL certificate file
--dep-up run helm dependency update before installing the chart --dep-up run helm dependency update before installing the chart
@ -93,6 +94,7 @@ helm install [CHART] [flags]
--no-crd-hook prevent CRD hooks from running, but run other hooks --no-crd-hook prevent CRD hooks from running, but run other hooks
--no-hooks prevent hooks from running during install --no-hooks prevent hooks from running during install
--password string chart repository password where to locate the requested chart --password string chart repository password where to locate the requested chart
--render-subchart-notes render subchart notes along with the parent
--replace re-use the given name, even if that name is already used. This is unsafe in production --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 --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 stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
@ -128,4 +130,4 @@ helm install [CHART] [flags]
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 10-Aug-2018 ###### Auto generated by spf13/cobra on 28-Jan-2019

@ -9,7 +9,8 @@ This command rolls back a release to a previous revision.
The first argument of the rollback command is the name of a release, and the The first argument of the rollback command is the name of a release, and the
second is a revision (version) number. To see revision numbers, run second is a revision (version) number. To see revision numbers, run
'helm history RELEASE'. 'helm history RELEASE'. If you'd like to rollback to the previous release use
'helm rollback [RELEASE] 0'.
``` ```
@ -51,4 +52,4 @@ helm rollback [flags] [RELEASE] [REVISION]
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 10-Aug-2018 ###### Auto generated by spf13/cobra on 29-Jan-2019

@ -19,7 +19,7 @@ To customize the chart values, use any of
- '--set-string' to provide key=val forcing val to be stored as a string, - '--set-string' to provide key=val forcing val to be stored as a string,
- '--set-file' to provide key=path to read a single large value from a file at path. - '--set-file' to provide key=path to read a single large value from a file at path.
To edit or append to the existing customized values, add the To edit or append to the existing customized values, add the
'--reuse-values' flag, otherwise any existing customized values are ignored. '--reuse-values' flag, otherwise any existing customized values are ignored.
If no chart value arguments are provided on the command line, any existing customized values are carried If no chart value arguments are provided on the command line, any existing customized values are carried
@ -65,6 +65,7 @@ helm upgrade [RELEASE] [CHART] [flags]
### Options ### Options
``` ```
--atomic if set, upgrade process rolls back changes made in case of failed upgrade, also sets --wait flag
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file --cert-file string identify HTTPS client using this SSL certificate file
--description string specify the description to use for the upgrade, rather than the default --description string specify the description to use for the upgrade, rather than the default
@ -79,6 +80,7 @@ helm upgrade [RELEASE] [CHART] [flags]
--no-hooks disable pre/post upgrade hooks --no-hooks disable pre/post upgrade hooks
--password string chart repository password where to locate the requested chart --password string chart repository password where to locate the requested chart
--recreate-pods performs pods restart for the resource if applicable --recreate-pods performs pods restart for the resource if applicable
--render-subchart-notes render subchart notes along with parent
--repo string chart repository url where to locate the requested chart --repo string chart repository url where to locate the requested chart
--reset-values when upgrading, reset the values to the ones built into the chart --reset-values when upgrading, reset the values to the ones built into the chart
--reuse-values 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. --reuse-values 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.
@ -115,4 +117,4 @@ helm upgrade [RELEASE] [CHART] [flags]
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 24-Aug-2018 ###### Auto generated by spf13/cobra on 28-Jan-2019

@ -45,7 +45,7 @@ brew install kubernetes-helm
(Note: There is also a formula for emacs-helm, which is a different (Note: There is also a formula for emacs-helm, which is a different
project.) project.)
### From Chocolatey (Windows) ### From Chocolatey or scoop (Windows)
Members of the Kubernetes community have contributed a [Helm package](https://chocolatey.org/packages/kubernetes-helm) build to Members of the Kubernetes community have contributed a [Helm package](https://chocolatey.org/packages/kubernetes-helm) build to
[Chocolatey](https://chocolatey.org/). This package is generally up to date. [Chocolatey](https://chocolatey.org/). This package is generally up to date.
@ -54,6 +54,12 @@ Members of the Kubernetes community have contributed a [Helm package](https://ch
choco install kubernetes-helm choco install kubernetes-helm
``` ```
The binary can also be installed via [`scoop`](https://scoop.sh) command-line installer.
```
scoop install helm
```
## From Script ## From Script
Helm now has an installer script that will automatically grab the latest version Helm now has an installer script that will automatically grab the latest version

@ -53,6 +53,7 @@ or [pull request](https://github.com/helm/helm/pulls).
- [helm-stop](https://github.com/IBM/helm-stop) - Plugin for stopping a release pods - [helm-stop](https://github.com/IBM/helm-stop) - Plugin for stopping a release pods
- [helm-template](https://github.com/technosophos/helm-template) - Debug/render templates client-side - [helm-template](https://github.com/technosophos/helm-template) - Debug/render templates client-side
- [helm-tiller](https://github.com/adamreese/helm-tiller) - Additional commands to work with Tiller - [helm-tiller](https://github.com/adamreese/helm-tiller) - Additional commands to work with Tiller
- [helm-tiller-info](https://github.com/maorfr/helm-tiller-info) - Plugin which prints information about Tiller
- [helm-unittest](https://github.com/lrills/helm-unittest) - Plugin for unit testing chart locally with YAML - [helm-unittest](https://github.com/lrills/helm-unittest) - Plugin for unit testing chart locally with YAML
- [Tillerless Helm v2](https://github.com/rimusz/helm-tiller) - Helm plugin for using Tiller locally and in CI/CD pipelines - [Tillerless Helm v2](https://github.com/rimusz/helm-tiller) - Helm plugin for using Tiller locally and in CI/CD pipelines
@ -88,7 +89,6 @@ Tools layered on top of Helm or Tiller.
Platforms, distributions, and services that include Helm support. Platforms, distributions, and services that include Helm support.
- [Cabin](http://www.skippbox.com/cabin/) - Mobile App for Managing Kubernetes
- [Fabric8](https://fabric8.io) - Integrated development platform for Kubernetes - [Fabric8](https://fabric8.io) - Integrated development platform for Kubernetes
- [Jenkins X](http://jenkins-x.io/) - open source automated CI/CD for Kubernetes which uses Helm for [promoting](http://jenkins-x.io/about/features/#promotion) applications through [environments via GitOps](http://jenkins-x.io/about/features/#environments) - [Jenkins X](http://jenkins-x.io/) - open source automated CI/CD for Kubernetes which uses Helm for [promoting](http://jenkins-x.io/about/features/#promotion) applications through [environments via GitOps](http://jenkins-x.io/about/features/#environments)
- [Kubernetic](https://kubernetic.com/) - Kubernetes Desktop Client - [Kubernetic](https://kubernetic.com/) - Kubernetes Desktop Client

@ -3,8 +3,8 @@
**IMPORTANT**: If your experience deviates from this document, please document the changes to keep it up-to-date. **IMPORTANT**: If your experience deviates from this document, please document the changes to keep it up-to-date.
## Release Meetings ## Release Meetings
As part of the release process, two of the weekly developer calls will be co-opted As part of the release process, two of the weekly developer calls will be
as "release meetings." co-opted as "release meetings."
### Start of the Release Cycle ### Start of the Release Cycle
The first developer call after a release will be used as the release meeting to The first developer call after a release will be used as the release meeting to
@ -17,17 +17,19 @@ identified:
- Any other important details for the community - Any other important details for the community
All of this information should be added to the GitHub milestone for the given All of this information should be added to the GitHub milestone for the given
release. This should give the community and maintainers a clear set of guidelines release. This should give the community and maintainers a clear set of
to follow when choosing whether or not to add issues and PRs to a given release. guidelines to follow when choosing whether or not to add issues and PRs to a
given release.
### End (almost) of the Release Cycle ### End (almost) of the Release Cycle
The developer call closest to two weeks before the scheduled release date will The developer call closest to two weeks before the scheduled release date will
be used to review any remaining PRs that should be pulled into the release. This be used to review any remaining PRs that should be pulled into the release. This
is the place to debate whether or not we should wait before cutting a release and is the place to debate whether or not we should wait before cutting a release
any other concerns. At the end of this meeting, if the release date has not been and any other concerns. At the end of this meeting, if the release date has not
pushed out, the first RC should be cut. Subsequent developer calls in between this been pushed out, the first RC should be cut. Subsequent developer calls in
meeting and the release date should have some time set aside to see if any bugs between this meeting and the release date should have some time set aside to see
were found. Once the release date is reached, the final release can be cut if any bugs were found. Once the release date is reached, the final release can
be cut
## A Maintainer's Guide to Releasing Helm ## A Maintainer's Guide to Releasing Helm
@ -37,17 +39,28 @@ So you're in charge of a new release for Helm? Cool. Here's what to do...
Just kidding! :trollface: Just kidding! :trollface:
All releases will be of the form vX.Y.Z where X is the major version number, Y is the minor version number and Z is the patch release number. This project strictly follows [semantic versioning](http://semver.org/) so following this step is critical. All releases will be of the form vX.Y.Z where X is the major version number, Y
is the minor version number and Z is the patch release number. This project
strictly follows [semantic versioning](http://semver.org/) so following this
step is critical.
It is important to note that this document assumes that the git remote in your repository that corresponds to "https://github.com/helm/helm" is named "upstream". If yours is not (for example, if you've chosen to name it "origin" or something similar instead), be sure to adjust the listed snippets for your local environment accordingly. If you are not sure what your upstream remote is named, use a command like `git remote -v` to find out. It is important to note that this document assumes that the git remote in your
repository that corresponds to "https://github.com/helm/helm" is named
"upstream". If yours is not (for example, if you've chosen to name it "origin"
or something similar instead), be sure to adjust the listed snippets for your
local environment accordingly. If you are not sure what your upstream remote is
named, use a command like `git remote -v` to find out.
If you don't have an upstream remote, you can add one easily using something like: If you don't have an upstream remote, you can add one easily using something
like:
```shell ```shell
git remote add upstream git@github.com:helm/helm.git git remote add upstream git@github.com:helm/helm.git
``` ```
In this doc, we are going to reference a few environment variables as well, which you may want to set for convenience. For major/minor releases, use the following: In this doc, we are going to reference a few environment variables as well,
which you may want to set for convenience. For major/minor releases, use the
following:
```shell ```shell
export RELEASE_NAME=vX.Y.0 export RELEASE_NAME=vX.Y.0
@ -68,7 +81,10 @@ export RELEASE_CANDIDATE_NAME="$RELEASE_NAME-rc.1"
### Major/Minor Releases ### Major/Minor Releases
Major releases are for new feature additions and behavioral changes *that break backwards compatibility*. Minor releases are for new feature additions that do not break backwards compatibility. To create a major or minor release, start by creating a `release-vX.Y.0` branch from master. Major releases are for new feature additions and behavioral changes *that break
backwards compatibility*. Minor releases are for new feature additions that do
not break backwards compatibility. To create a major or minor release, start by
creating a `release-vX.Y.0` branch from master.
```shell ```shell
git fetch upstream git fetch upstream
@ -76,11 +92,13 @@ git checkout upstream/master
git checkout -b $RELEASE_BRANCH_NAME git checkout -b $RELEASE_BRANCH_NAME
``` ```
This new branch is going to be the base for the release, which we are going to iterate upon later. This new branch is going to be the base for the release, which we are going to
iterate upon later.
### Patch releases ### Patch releases
Patch releases are a few critical cherry-picked fixes to existing releases. Start by creating a `release-vX.Y.Z` branch from the latest patch release. Patch releases are a few critical cherry-picked fixes to existing releases.
Start by creating a `release-vX.Y.Z` branch from the latest patch release.
```shell ```shell
git fetch upstream --tags git fetch upstream --tags
@ -88,7 +106,8 @@ git checkout $PREVIOUS_PATCH_RELEASE
git checkout -b $RELEASE_BRANCH_NAME git checkout -b $RELEASE_BRANCH_NAME
``` ```
From here, we can cherry-pick the commits we want to bring into the patch release: From here, we can cherry-pick the commits we want to bring into the patch
release:
```shell ```shell
# get the commits ids we want to cherry-pick # get the commits ids we want to cherry-pick
@ -98,11 +117,13 @@ git cherry-pick -x <commit-id>
git cherry-pick -x <commit-id> git cherry-pick -x <commit-id>
``` ```
This new branch is going to be the base for the release, which we are going to iterate upon later. This new branch is going to be the base for the release, which we are going to
iterate upon later.
## 2. Change the Version Number in Git ## 2. Change the Version Number in Git
When doing a minor release, make sure to update pkg/version/version.go with the new release version. When doing a minor release, make sure to update pkg/version/version.go with the
new release version.
```shell ```shell
$ git diff pkg/version/version.go $ git diff pkg/version/version.go
@ -128,28 +149,36 @@ git commit -m "bump version to $RELEASE_CANDIDATE_NAME"
## 3. Commit and Push the Release Branch ## 3. Commit and Push the Release Branch
In order for others to start testing, we can now push the release branch upstream and start the test process. In order for others to start testing, we can now push the release branch
upstream and start the test process.
```shell ```shell
git push upstream $RELEASE_BRANCH_NAME git push upstream $RELEASE_BRANCH_NAME
``` ```
Make sure to check [helm on CircleCI](https://circleci.com/gh/helm/helm) and make sure the release passed CI before proceeding. Make sure to check [helm on CircleCI](https://circleci.com/gh/helm/helm) and
make sure the release passed CI before proceeding.
If anyone is available, let others peer-review the branch before continuing to ensure that all the proper changes have been made and all of the commits for the release are there. If anyone is available, let others peer-review the branch before continuing to
ensure that all the proper changes have been made and all of the commits for the
release are there.
## 4. Create a Release Candidate ## 4. Create a Release Candidate
Now that the release branch is out and ready, it is time to start creating and iterating on release candidates. Now that the release branch is out and ready, it is time to start creating and
iterating on release candidates.
```shell ```shell
git tag --sign --annotate "${RELEASE_CANDIDATE_NAME}" --message "Helm release ${RELEASE_CANDIDATE_NAME}" git tag --sign --annotate "${RELEASE_CANDIDATE_NAME}" --message "Helm release ${RELEASE_CANDIDATE_NAME}"
git push upstream $RELEASE_CANDIDATE_NAME git push upstream $RELEASE_CANDIDATE_NAME
``` ```
CircleCI will automatically create a tagged release image and client binary to test with. CircleCI will automatically create a tagged release image and client binary to
test with.
For testers, the process to start testing after CircleCI finishes building the artifacts involves the following steps to grab the client from Google Cloud Storage: For testers, the process to start testing after CircleCI finishes building the
artifacts involves the following steps to grab the client from Google Cloud
Storage:
linux/amd64, using /bin/bash: linux/amd64, using /bin/bash:
@ -169,21 +198,35 @@ windows/amd64, using PowerShell:
PS C:\> Invoke-WebRequest -Uri "https://kubernetes-helm.storage.googleapis.com/helm-$RELEASE_CANDIDATE_NAME-windows-amd64.zip" -OutFile "helm-$ReleaseCandidateName-windows-amd64.zip" PS C:\> Invoke-WebRequest -Uri "https://kubernetes-helm.storage.googleapis.com/helm-$RELEASE_CANDIDATE_NAME-windows-amd64.zip" -OutFile "helm-$ReleaseCandidateName-windows-amd64.zip"
``` ```
Then, unpack and move the binary to somewhere on your $PATH, or move it somewhere and add it to your $PATH (e.g. /usr/local/bin/helm for linux/macOS, C:\Program Files\helm\helm.exe for Windows). Then, unpack and move the binary to somewhere on your $PATH, or move it
somewhere and add it to your $PATH (e.g. /usr/local/bin/helm for linux/macOS,
C:\Program Files\helm\helm.exe for Windows).
## 5. Iterate on Successive Release Candidates ## 5. Iterate on Successive Release Candidates
Spend several days explicitly investing time and resources to try and break helm in every possible way, documenting any findings pertinent to the release. This time should be spent testing and finding ways in which the release might have caused various features or upgrade environments to have issues, not coding. During this time, the release is in code freeze, and any additional code changes will be pushed out to the next release. Spend several days explicitly investing time and resources to try and break helm
in every possible way, documenting any findings pertinent to the release. This
time should be spent testing and finding ways in which the release might have
caused various features or upgrade environments to have issues, not coding.
During this time, the release is in code freeze, and any additional code changes
will be pushed out to the next release.
During this phase, the $RELEASE_BRANCH_NAME branch will keep evolving as you will produce new release candidates. The frequency of new candidates is up to the release manager: use your best judgement taking into account the severity of reported issues, testers' availability, and the release deadline date. Generally speaking, it is better to let a release roll over the deadline than to ship a broken release. During this phase, the $RELEASE_BRANCH_NAME branch will keep evolving as you
will produce new release candidates. The frequency of new candidates is up to
the release manager: use your best judgement taking into account the severity of
reported issues, testers' availability, and the release deadline date. Generally
speaking, it is better to let a release roll over the deadline than to ship a
broken release.
Each time you'll want to produce a new release candidate, you will start by adding commits to the branch by cherry-picking from master: Each time you'll want to produce a new release candidate, you will start by
adding commits to the branch by cherry-picking from master:
```shell ```shell
git cherry-pick -x <commit_id> git cherry-pick -x <commit_id>
``` ```
You will also want to update the release version number and the CHANGELOG as we did in steps 2 and 3 as separate commits. You will also want to update the release version number and the CHANGELOG as we
did in steps 2 and 3 as separate commits.
After that, tag it and notify users of the new release candidate: After that, tag it and notify users of the new release candidate:
@ -197,7 +240,9 @@ From here on just repeat this process, continuously testing until you're happy w
## 6. Finalize the Release ## 6. Finalize the Release
When you're finally happy with the quality of a release candidate, you can move on and create the real thing. Double-check one last time to make sure everything is in order, then finally push the release tag. When you're finally happy with the quality of a release candidate, you can move
on and create the real thing. Double-check one last time to make sure everything
is in order, then finally push the release tag.
```shell ```shell
git checkout $RELEASE_BRANCH_NAME git checkout $RELEASE_BRANCH_NAME
@ -207,9 +252,13 @@ git push upstream $RELEASE_NAME
## 7. Write the Release Notes ## 7. Write the Release Notes
We will auto-generate a changelog based on the commits that occurred during a release cycle, but it is usually more beneficial to the end-user if the release notes are hand-written by a human being/marketing team/dog. We will auto-generate a changelog based on the commits that occurred during a
release cycle, but it is usually more beneficial to the end-user if the release
notes are hand-written by a human being/marketing team/dog.
If you're releasing a major/minor release, listing notable user-facing features is usually sufficient. For patch releases, do the same, but make note of the symptoms and who is affected. If you're releasing a major/minor release, listing notable user-facing features
is usually sufficient. For patch releases, do the same, but make note of the
symptoms and who is affected.
An example release note for a minor release would look like this: An example release note for a minor release would look like this:
@ -226,6 +275,13 @@ The community keeps growing, and we'd love to see you there!
- Hang out at the Public Developer Call: Thursday, 9:30 Pacific via [Zoom](https://zoom.us/j/696660622) - Hang out at the Public Developer Call: Thursday, 9:30 Pacific via [Zoom](https://zoom.us/j/696660622)
- Test, debug, and contribute charts: [GitHub/helm/charts](https://github.com/helm/charts) - Test, debug, and contribute charts: [GitHub/helm/charts](https://github.com/helm/charts)
## Features and Changes
- Major
- features
- list
- here
## Installation and Upgrading ## Installation and Upgrading
Download Helm X.Y. The common platform binaries are here: Download Helm X.Y. The common platform binaries are here:
@ -250,23 +306,45 @@ The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will g
## Changelog ## Changelog
- chore(*): bump version to v2.7.0 08c1144f5eb3e3b636d9775617287cc26e53dba4 (Adam Reese) ### Features
- ref(*): kubernetes v1.11 support efadbd88035654b2951f3958167afed014c46bc6 (Adam Reese)
- feat(helm): add $HELM_KEY_PASSPHRASE environment variable for signing helm charts (#4778) 1e26b5300b5166fabb90002535aacd2f9cc7d787
### Bug fixes
- fix circle not building tags f4f932fabd197f7e6d608c8672b33a483b4b76fa (Matthew Fisher) - fix circle not building tags f4f932fabd197f7e6d608c8672b33a483b4b76fa (Matthew Fisher)
### Code cleanup
- ref(kube): Gets rid of superfluous Sprintf call 3071a16f5eb3a2b646d9795617287cc26e53dba4 (Taylor Thomas)
- chore(*): bump version to v2.7.0 08c1144f5eb3e3b636d9775617287cc26e53dba4 (Adam Reese)
### Documentation Changes
- docs(release_checklist): fix changelog generation command (#4694) 8442851a5c566a01d9b4c69b368d64daa04f6a7f (Matthew Fisher)
``` ```
The changelog at the bottom of the release notes can be generated with this command: The changelog at the bottom of the release notes can be generated with this
command:
```shell ```shell
PREVIOUS_RELEASE=vX.Y.Z PREVIOUS_RELEASE=vX.Y.Z
git log --no-merges --pretty=format:'- %s %H (%aN)' $PREVIOUS_RELEASE..$RELEASE_NAME git log --no-merges --pretty=format:'- %s %H (%aN)' $PREVIOUS_RELEASE..$RELEASE_NAME
``` ```
Once finished, go into GitHub and edit the release notes for the tagged release with the notes written here. After generating the changelog, you will need to categorize the changes as shown
in the example above.
Once finished, go into GitHub and edit the release notes for the tagged release
with the notes written here.
## 8. Evangelize ## 8. Evangelize
Congratulations! You're done. Go grab yourself a $DRINK_OF_CHOICE. You've earned it. Congratulations! You're done. Go grab yourself a $DRINK_OF_CHOICE. You've earned
it.
After enjoying a nice $DRINK_OF_CHOICE, go forth and announce the glad tidings of the new release in Slack and on Twitter. You should also notify any key partners in the helm community such as the homebrew formula maintainers, the owners of incubator projects (e.g. ChartMuseum) and any other interested parties. After enjoying a nice $DRINK_OF_CHOICE, go forth and announce the glad tidings
of the new release in Slack and on Twitter. You should also notify any key
partners in the helm community such as the homebrew formula maintainers, the
owners of incubator projects (e.g. ChartMuseum) and any other interested
parties.
Optionally, write a blog post about the new release and showcase some of the new features on there! Optionally, write a blog post about the new release and showcase some of the new
features on there!

@ -69,9 +69,10 @@ When Helm clients are connecting from outside of the cluster, the security betwe
Contrary to the previous [Enabling TLS](#enabling-tls) section, this section does not involve running a tiller server pod in your cluster (for what it's worth, that lines up with the current [helm v3 proposal](https://github.com/helm/community/blob/master/helm-v3/000-helm-v3.md)), thus there is no gRPC endpoint (and thus there's no need to create & manage TLS certificates to secure each gRPC endpoint). Contrary to the previous [Enabling TLS](#enabling-tls) section, this section does not involve running a tiller server pod in your cluster (for what it's worth, that lines up with the current [helm v3 proposal](https://github.com/helm/community/blob/master/helm-v3/000-helm-v3.md)), thus there is no gRPC endpoint (and thus there's no need to create & manage TLS certificates to secure each gRPC endpoint).
Steps: Steps:
* Fetch the latest helm release tarball from the [GitHub release page](https://github.com/helm/helm/releases), and extract and move `helm` and `tiller` somewhere on your `$PATH`.
* "Server": Run `tiller --storage=secret`. (Note that `tiller` has a default value of ":44134" for the `--listen` argument.) - Fetch the latest helm release tarball from the [GitHub release page](https://github.com/helm/helm/releases), and extract and move `helm` and `tiller` somewhere on your `$PATH`.
* Client: In another terminal (and on the same host that the aforementioned `tiller` command was run for the previous bullet): Run `export HELM_HOST=:44134`, and then run `helm` commands as usual. - "Server": Run `tiller --storage=secret`. (Note that `tiller` has a default value of ":44134" for the `--listen` argument.)
- Client: In another terminal (and on the same host that the aforementioned `tiller` command was run for the previous bullet): Run `export HELM_HOST=:44134`, and then run `helm` commands as usual.
### Tiller's Release Information ### Tiller's Release Information

@ -1,4 +1,4 @@
# Using Helm # Using Helm
This guide explains the basics of using Helm (and Tiller) to manage This guide explains the basics of using Helm (and Tiller) to manage
packages on your Kubernetes cluster. It assumes that you have already packages on your Kubernetes cluster. It assumes that you have already
@ -215,7 +215,10 @@ You can then override any of these settings in a YAML formatted file,
and then pass that file during installation. and then pass that file during installation.
```console ```console
$ echo '{mariadbUser: user0, mariadbDatabase: user0db}' > config.yaml $ cat << EOF > config.yaml
mariadbUser: user0
mariadbDatabase: user0db
EOF
$ helm install -f config.yaml stable/mariadb $ helm install -f config.yaml stable/mariadb
``` ```

@ -58,14 +58,6 @@ const defaultValues = `# Default values for %s.
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
nameOverride: ""
fullnameOverride: ""
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
replicaCount: 1 replicaCount: 1
hpa: hpa:
@ -75,6 +67,43 @@ hpa:
targetCPUUtilizationPercentage: 80 targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80 targetMemoryUtilizationPercentage: 80
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
volumes: [] volumes: []
# - name: cache-volume # - name: cache-volume
# emptyDir: {} # emptyDir: {}
@ -94,18 +123,6 @@ securityContext: {}
# runAsNonRoot: true # runAsNonRoot: true
# runAsUser: 10001 # runAsUser: 10001
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
env: [] env: []
# - name: FOO # - name: FOO
# value: bar # value: bar
@ -126,23 +143,6 @@ nodeSelector: {}
tolerations: [] tolerations: []
affinity: {} affinity: {}
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
paths: []
hosts:
- chart-example.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
` `
const defaultIgnore = `# Patterns to ignore when building packages. const defaultIgnore = `# Patterns to ignore when building packages.
@ -171,7 +171,6 @@ const defaultIgnore = `# Patterns to ignore when building packages.
const defaultIngress = `{{- if .Values.ingress.enabled -}} const defaultIngress = `{{- if .Values.ingress.enabled -}}
{{- $fullName := include "<CHARTNAME>.fullname" . -}} {{- $fullName := include "<CHARTNAME>.fullname" . -}}
{{- $ingressPaths := .Values.ingress.paths -}}
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Ingress kind: Ingress
metadata: metadata:
@ -198,10 +197,10 @@ spec:
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range .Values.ingress.hosts }}
- host: {{ . | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:
{{- range $ingressPaths }} {{- range .paths }}
- path: {{ . }} - path: {{ . }}
backend: backend:
serviceName: {{ $fullName }} serviceName: {{ $fullName }}
@ -232,14 +231,10 @@ spec:
app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }} app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
spec: spec:
{{- with .Values.podSecurityContext }}
securityContext: securityContext:
{{- toYaml . | nindent 8 }} {{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- end }}
{{- with .Values.volumes }}
volumes: volumes:
{{- toYaml . | nindent 8 }} {{- toYaml .Values.volumes | nindent 8 }}
{{- end }}
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
@ -330,8 +325,8 @@ spec:
const defaultNotes = `1. Get the application URL by running these commands: const defaultNotes = `1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }} {{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }} {{- range $host := .Values.ingress.hosts }}
{{- range $.Values.ingress.paths }} {{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ . }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- else if contains "NodePort" .Values.service.type }} {{- else if contains "NodePort" .Values.service.type }}

@ -17,58 +17,60 @@ limitations under the License.
package chartutil package chartutil
import ( import (
"archive/tar" "errors"
"compress/gzip"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
securejoin "github.com/cyphar/filepath-securejoin"
) )
// Expand uncompresses and extracts a chart into the specified directory. // Expand uncompresses and extracts a chart into the specified directory.
func Expand(dir string, r io.Reader) error { func Expand(dir string, r io.Reader) error {
gr, err := gzip.NewReader(r) files, err := loadArchiveFiles(r)
if err != nil { if err != nil {
return err return err
} }
defer gr.Close()
tr := tar.NewReader(gr)
for {
header, err := tr.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
//split header name and create missing directories // Get the name of the chart
d, _ := filepath.Split(header.Name) var chartName string
fullDir := filepath.Join(dir, d) for _, file := range files {
_, err = os.Stat(fullDir) if file.Name == "Chart.yaml" {
if err != nil && d != "" { ch, err := UnmarshalChartfile(file.Data)
if err := os.MkdirAll(fullDir, 0700); err != nil { if err != nil {
return err return err
} }
chartName = ch.GetName()
} }
}
if chartName == "" {
return errors.New("chart name not specified")
}
path := filepath.Clean(filepath.Join(dir, header.Name)) // Find the base directory
info := header.FileInfo() chartdir, err := securejoin.SecureJoin(dir, chartName)
if info.IsDir() { if err != nil {
if err = os.MkdirAll(path, info.Mode()); err != nil { return err
return err }
}
continue
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) // Copy all files verbatim. We don't parse these files because parsing can remove
// comments.
for _, file := range files {
outpath, err := securejoin.SecureJoin(chartdir, file.Name)
if err != nil { if err != nil {
return err return err
} }
_, err = io.Copy(file, tr)
if err != nil { // Make sure the necessary subdirs get created.
file.Close() basedir := filepath.Dir(outpath)
if err := os.MkdirAll(basedir, 0755); err != nil {
return err
}
if err := ioutil.WriteFile(outpath, file.Data, 0644); err != nil {
return err return err
} }
file.Close()
} }
return nil return nil
} }

@ -0,0 +1,121 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestExpand(t *testing.T) {
dest, err := ioutil.TempDir("", "helm-testing-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dest)
reader, err := os.Open("testdata/frobnitz-1.2.3.tgz")
if err != nil {
t.Fatal(err)
}
if err := Expand(dest, reader); err != nil {
t.Fatal(err)
}
expectedChartPath := filepath.Join(dest, "frobnitz")
fi, err := os.Stat(expectedChartPath)
if err != nil {
t.Fatal(err)
}
if !fi.IsDir() {
t.Fatalf("expected a chart directory at %s", expectedChartPath)
}
dir, err := os.Open(expectedChartPath)
if err != nil {
t.Fatal(err)
}
fis, err := dir.Readdir(0)
if err != nil {
t.Fatal(err)
}
expectLen := 12
if len(fis) != expectLen {
t.Errorf("Expected %d files, but got %d", expectLen, len(fis))
}
for _, fi := range fis {
expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name()))
if err != nil {
t.Fatal(err)
}
if fi.Size() != expect.Size() {
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size())
}
}
}
func TestExpandFile(t *testing.T) {
dest, err := ioutil.TempDir("", "helm-testing-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dest)
if err := ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"); err != nil {
t.Fatal(err)
}
expectedChartPath := filepath.Join(dest, "frobnitz")
fi, err := os.Stat(expectedChartPath)
if err != nil {
t.Fatal(err)
}
if !fi.IsDir() {
t.Fatalf("expected a chart directory at %s", expectedChartPath)
}
dir, err := os.Open(expectedChartPath)
if err != nil {
t.Fatal(err)
}
fis, err := dir.Readdir(0)
if err != nil {
t.Fatal(err)
}
expectLen := 12
if len(fis) != expectLen {
t.Errorf("Expected %d files, but got %d", expectLen, len(fis))
}
for _, fi := range fis {
expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name()))
if err != nil {
t.Fatal(err)
}
if fi.Size() != expect.Size() {
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size())
}
}
}

@ -25,7 +25,9 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/any"
@ -63,11 +65,13 @@ type BufferedFile struct {
Data []byte Data []byte
} }
// LoadArchive loads from a reader containing a compressed tar archive. var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`)
func LoadArchive(in io.Reader) (*chart.Chart, error) {
// loadArchiveFiles loads files out of an archive
func loadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
unzipped, err := gzip.NewReader(in) unzipped, err := gzip.NewReader(in)
if err != nil { if err != nil {
return &chart.Chart{}, err return nil, err
} }
defer unzipped.Close() defer unzipped.Close()
@ -80,7 +84,7 @@ func LoadArchive(in io.Reader) (*chart.Chart, error) {
break break
} }
if err != nil { if err != nil {
return &chart.Chart{}, err return nil, err
} }
if hd.FileInfo().IsDir() { if hd.FileInfo().IsDir() {
@ -89,6 +93,12 @@ func LoadArchive(in io.Reader) (*chart.Chart, error) {
continue continue
} }
switch hd.Typeflag {
// We don't want to process these extension header files.
case tar.TypeXGlobalHeader, tar.TypeXHeader:
continue
}
// Archive could contain \ if generated on Windows // Archive could contain \ if generated on Windows
delimiter := "/" delimiter := "/"
if strings.ContainsRune(hd.Name, '\\') { if strings.ContainsRune(hd.Name, '\\') {
@ -101,12 +111,33 @@ func LoadArchive(in io.Reader) (*chart.Chart, error) {
// Normalize the path to the / delimiter // Normalize the path to the / delimiter
n = strings.Replace(n, delimiter, "/", -1) n = strings.Replace(n, delimiter, "/", -1)
if path.IsAbs(n) {
return nil, errors.New("chart illegally contains absolute paths")
}
n = path.Clean(n)
if n == "." {
// In this case, the original path was relative when it should have been absolute.
return nil, errors.New("chart illegally contains empty path")
}
if strings.HasPrefix(n, "..") {
return nil, errors.New("chart illegally references parent directory")
}
// In some particularly arcane acts of path creativity, it is possible to intermix
// UNIX and Windows style paths in such a way that you produce a result of the form
// c:/foo even after all the built-in absolute path checks. So we explicitly check
// for this condition.
if drivePathPattern.MatchString(n) {
return nil, errors.New("chart contains illegally named files")
}
if parts[0] == "Chart.yaml" { if parts[0] == "Chart.yaml" {
return nil, errors.New("chart yaml not in base directory") return nil, errors.New("chart yaml not in base directory")
} }
if _, err := io.Copy(b, tr); err != nil { if _, err := io.Copy(b, tr); err != nil {
return &chart.Chart{}, err return files, err
} }
files = append(files, &BufferedFile{Name: n, Data: b.Bytes()}) files = append(files, &BufferedFile{Name: n, Data: b.Bytes()})
@ -116,7 +147,15 @@ func LoadArchive(in io.Reader) (*chart.Chart, error) {
if len(files) == 0 { if len(files) == 0 {
return nil, errors.New("no files in chart archive") return nil, errors.New("no files in chart archive")
} }
return files, nil
}
// LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (*chart.Chart, error) {
files, err := loadArchiveFiles(in)
if err != nil {
return nil, err
}
return LoadFiles(files) return LoadFiles(files)
} }

@ -17,8 +17,14 @@ limitations under the License.
package chartutil package chartutil
import ( import (
"archive/tar"
"compress/gzip"
"io/ioutil"
"os"
"path" "path"
"path/filepath"
"testing" "testing"
"time"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
) )
@ -43,6 +49,97 @@ func TestLoadFile(t *testing.T) {
verifyRequirements(t, c) verifyRequirements(t, c)
} }
func TestLoadArchive_InvalidArchive(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "helm-test-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpdir)
writeTar := func(filename, internalPath string, body []byte) {
dest, err := os.Create(filename)
if err != nil {
t.Fatal(err)
}
zipper := gzip.NewWriter(dest)
tw := tar.NewWriter(zipper)
h := &tar.Header{
Name: internalPath,
Mode: 0755,
Size: int64(len(body)),
ModTime: time.Now(),
}
if err := tw.WriteHeader(h); err != nil {
t.Fatal(err)
}
if _, err := tw.Write(body); err != nil {
t.Fatal(err)
}
tw.Close()
zipper.Close()
dest.Close()
}
for _, tt := range []struct {
chartname string
internal string
expectError string
}{
{"illegal-dots.tgz", "../../malformed-helm-test", "chart illegally references parent directory"},
{"illegal-dots2.tgz", "/foo/../../malformed-helm-test", "chart illegally references parent directory"},
{"illegal-dots3.tgz", "/../../malformed-helm-test", "chart illegally references parent directory"},
{"illegal-dots4.tgz", "./../../malformed-helm-test", "chart illegally references parent directory"},
{"illegal-name.tgz", "./.", "chart illegally contains empty path"},
{"illegal-name2.tgz", "/./.", "chart illegally contains empty path"},
{"illegal-name3.tgz", "missing-leading-slash", "chart illegally contains empty path"},
{"illegal-name4.tgz", "/missing-leading-slash", "chart metadata (Chart.yaml) missing"},
{"illegal-abspath.tgz", "//foo", "chart illegally contains absolute paths"},
{"illegal-abspath2.tgz", "///foo", "chart illegally contains absolute paths"},
{"illegal-abspath3.tgz", "\\\\foo", "chart illegally contains absolute paths"},
{"illegal-abspath3.tgz", "\\..\\..\\foo", "chart illegally references parent directory"},
// Under special circumstances, this can get normalized to things that look like absolute Windows paths
{"illegal-abspath4.tgz", "\\.\\c:\\\\foo", "chart contains illegally named files"},
{"illegal-abspath5.tgz", "/./c://foo", "chart contains illegally named files"},
{"illegal-abspath6.tgz", "\\\\?\\Some\\windows\\magic", "chart illegally contains absolute paths"},
} {
illegalChart := filepath.Join(tmpdir, tt.chartname)
writeTar(illegalChart, tt.internal, []byte("hello: world"))
_, err = Load(illegalChart)
if err == nil {
t.Fatal("expected error when unpacking illegal files")
}
if err.Error() != tt.expectError {
t.Errorf("Expected %q, got %q for %s", tt.expectError, err.Error(), tt.chartname)
}
}
// Make sure that absolute path gets interpreted as relative
illegalChart := filepath.Join(tmpdir, "abs-path.tgz")
writeTar(illegalChart, "/Chart.yaml", []byte("hello: world"))
_, err = Load(illegalChart)
if err.Error() != "invalid chart (Chart.yaml): name must not be empty" {
t.Error(err)
}
// And just to validate that the above was not spurious
illegalChart = filepath.Join(tmpdir, "abs-path2.tgz")
writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world"))
_, err = Load(illegalChart)
if err.Error() != "chart metadata (Chart.yaml) missing" {
t.Error(err)
}
// Finally, test that drive letter gets stripped off on Windows
illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz")
writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world"))
_, err = Load(illegalChart)
if err.Error() != "invalid chart (Chart.yaml): name must not be empty" {
t.Error(err)
}
}
func TestLoadFiles(t *testing.T) { func TestLoadFiles(t *testing.T) {
goodFiles := []*BufferedFile{ goodFiles := []*BufferedFile{
{ {

@ -302,7 +302,7 @@ func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-ch
return h.test(ctx, req) return h.test(ctx, req)
} }
// PingTiller pings the Tiller pod and ensure's that it is up and running // PingTiller pings the Tiller pod and ensures that it is up and running
func (h *Client) PingTiller() error { func (h *Client) PingTiller() error {
ctx := NewContext() ctx := NewContext()
return h.ping(ctx) return h.ping(ctx)

@ -257,7 +257,7 @@ func (c *FakeClient) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (
return results, errc return results, errc
} }
// PingTiller pings the Tiller pod and ensure's that it is up and running // PingTiller pings the Tiller pod and ensures that it is up and running
func (c *FakeClient) PingTiller() error { func (c *FakeClient) PingTiller() error {
return nil return nil
} }

@ -346,6 +346,20 @@ func InstallReuseName(reuse bool) InstallOption {
} }
} }
// InstallSubNotes will (if true) instruct Tiller to render SubChart Notes
func InstallSubNotes(enable bool) InstallOption {
return func(opts *options) {
opts.instReq.SubNotes = enable
}
}
// UpgradeSubNotes will (if true) instruct Tiller to render SubChart Notes
func UpgradeSubNotes(enable bool) UpdateOption {
return func(opts *options) {
opts.updateReq.SubNotes = enable
}
}
// RollbackDisableHooks will disable hooks for a rollback operation // RollbackDisableHooks will disable hooks for a rollback operation
func RollbackDisableHooks(disable bool) RollbackOption { func RollbackDisableHooks(disable bool) RollbackOption {
return func(opts *options) { return func(opts *options) {
@ -460,7 +474,7 @@ type VersionOption func(*options)
// the defaults used when running the `helm upgrade` command. // the defaults used when running the `helm upgrade` command.
type UpdateOption func(*options) type UpdateOption func(*options)
// RollbackOption allows specififying various settings configurable // RollbackOption allows specifying various settings configurable
// by the helm client user for overriding the defaults used when // by the helm client user for overriding the defaults used when
// running the `helm rollback` command. // running the `helm rollback` command.
type RollbackOption func(*options) type RollbackOption func(*options)

@ -23,11 +23,12 @@ import (
goerrors "errors" goerrors "errors"
"fmt" "fmt"
"io" "io"
"k8s.io/apimachinery/pkg/api/meta"
"log" "log"
"strings" "strings"
"time" "time"
jsonpatch "github.com/evanphx/json-patch" "github.com/evanphx/json-patch"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2" appsv1beta2 "k8s.io/api/apps/v1beta2"
@ -60,6 +61,8 @@ const MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n"
// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found.
var ErrNoObjectsVisited = goerrors.New("no objects visited") var ErrNoObjectsVisited = goerrors.New("no objects visited")
var metadataAccessor = meta.NewAccessor()
// Client represents a client capable of communicating with the Kubernetes API. // Client represents a client capable of communicating with the Kubernetes API.
type Client struct { type Client struct {
cmdutil.Factory cmdutil.Factory
@ -308,6 +311,19 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader
for _, info := range original.Difference(target) { for _, info := range original.Difference(target) {
c.Log("Deleting %q in %s...", info.Name, info.Namespace) c.Log("Deleting %q in %s...", info.Name, info.Namespace)
if err := info.Get(); err != nil {
c.Log("Unable to get obj %q, err: %s", info.Name, err)
}
annotations, err := metadataAccessor.Annotations(info.Object)
if err != nil {
c.Log("Unable to get annotations on %q, err: %s", info.Name, err)
}
if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy {
c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy)
continue
}
if err := deleteResource(info); err != nil { if err := deleteResource(info); err != nil {
c.Log("Failed to delete %q, err: %s", info.Name, err) c.Log("Failed to delete %q, err: %s", info.Name, err)
} }

@ -151,6 +151,8 @@ func TestUpdate(t *testing.T) {
return newResponse(200, &listB.Items[1]) return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "DELETE": case p == "/namespaces/default/pods/squid" && m == "DELETE":
return newResponse(200, &listB.Items[1]) return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "GET":
return newResponse(200, &listA.Items[2])
default: default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil return nil, nil
@ -183,6 +185,7 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods/otter:GET", "/namespaces/default/pods/otter:GET",
"/namespaces/default/pods/dolphin:GET", "/namespaces/default/pods/dolphin:GET",
"/namespaces/default/pods:POST", "/namespaces/default/pods:POST",
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE", "/namespaces/default/pods/squid:DELETE",
} }
if len(expectedActions) != len(actions) { if len(expectedActions) != len(actions) {
@ -194,6 +197,18 @@ func TestUpdate(t *testing.T) {
t.Errorf("expected %s request got %s", v, actions[k]) t.Errorf("expected %s request got %s", v, actions[k])
} }
} }
// Test resource policy is respected
actions = nil
listA.Items[2].ObjectMeta.Annotations = map[string]string{ResourcePolicyAnno: KeepPolicy}
if err := c.Update(v1.NamespaceDefault, objBody(&listA), objBody(&listB), false, false, 0, false); err != nil {
t.Fatal(err)
}
for _, v := range actions {
if v == "/namespaces/default/pods/squid:DELETE" {
t.Errorf("should not have deleted squid - it has helm.sh/resource-policy=keep")
}
}
} }
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {

@ -0,0 +1,26 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kube
// ResourcePolicyAnno is the annotation name for a resource policy
const ResourcePolicyAnno = "helm.sh/resource-policy"
// KeepPolicy is the resource policy type for keep
//
// This resource policy type allows resources to skip being deleted
// during an uninstallRelease action.
const KeepPolicy = "keep"

@ -46,7 +46,8 @@ func Chartfile(linter *support.Linter) {
return return
} }
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNamePresence(chartFile))
linter.RunLinterRule(support.WarningSev, chartFileName, validateChartNameFormat(chartFile))
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile)) linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile))
// Chart metadata // Chart metadata
@ -74,13 +75,20 @@ func validateChartYamlFormat(chartFileError error) error {
return nil return nil
} }
func validateChartName(cf *chart.Metadata) error { func validateChartNamePresence(cf *chart.Metadata) error {
if cf.Name == "" { if cf.Name == "" {
return errors.New("name is required") return errors.New("name is required")
} }
return nil return nil
} }
func validateChartNameFormat(cf *chart.Metadata) error {
if strings.Contains(cf.Name, ".") {
return errors.New("name should be lower case letters and numbers. Words may be separated with dashes")
}
return nil
}
func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error { func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error {
if cf.Name != filepath.Base(chartDir) { if cf.Name != filepath.Base(chartDir) {
return fmt.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name) return fmt.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name)

@ -29,17 +29,20 @@ import (
) )
const ( const (
badChartDir = "testdata/badchartfile" badChartDir = "testdata/badchartfile"
goodChartDir = "testdata/goodone" badNameChartDir = "testdata/badnamechart"
goodChartDir = "testdata/goodone"
) )
var ( var (
badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") badChartFilePath = filepath.Join(badChartDir, "Chart.yaml")
badNameChartFilePath = filepath.Join(badNameChartDir, "Chart.yaml")
goodChartFilePath = filepath.Join(goodChartDir, "Chart.yaml") goodChartFilePath = filepath.Join(goodChartDir, "Chart.yaml")
nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml")
) )
var badChart, chatLoadRrr = chartutil.LoadChartfile(badChartFilePath) var badChart, chatLoadRrr = chartutil.LoadChartfile(badChartFilePath)
var badNameChart, _ = chartutil.LoadChartfile(badNameChartFilePath)
var goodChart, _ = chartutil.LoadChartfile(goodChartFilePath) var goodChart, _ = chartutil.LoadChartfile(goodChartFilePath)
// Validation functions Test // Validation functions Test
@ -66,12 +69,19 @@ func TestValidateChartYamlFormat(t *testing.T) {
} }
func TestValidateChartName(t *testing.T) { func TestValidateChartName(t *testing.T) {
err := validateChartName(badChart) err := validateChartNamePresence(badChart)
if err == nil { if err == nil {
t.Errorf("validateChartName to return a linter error, got no error") t.Errorf("validateChartName to return a linter error, got no error")
} }
} }
func TestValidateChartNameFormat(t *testing.T) {
err := validateChartNameFormat(badNameChart)
if err == nil {
t.Errorf("validateChartNameFormat to return a linter error, got no error")
}
}
func TestValidateChartNameDirMatch(t *testing.T) { func TestValidateChartNameDirMatch(t *testing.T) {
err := validateChartNameDirMatch(goodChartDir, goodChart) err := validateChartNameDirMatch(goodChartDir, goodChart)
if err != nil { if err != nil {

@ -0,0 +1,4 @@
name: bad.chart.name
description: A Helm chart for Kubernetes
version: 0.1.0
icon: http://riverrun.io

@ -0,0 +1 @@
# Default values for badchartname.

@ -381,6 +381,8 @@ type UpdateReleaseRequest struct {
Force bool `protobuf:"varint,11,opt,name=force" json:"force,omitempty"` Force bool `protobuf:"varint,11,opt,name=force" json:"force,omitempty"`
// Description, if set, will set the description for the updated release // Description, if set, will set the description for the updated release
Description string `protobuf:"bytes,12,opt,name=description" json:"description,omitempty"` Description string `protobuf:"bytes,12,opt,name=description" json:"description,omitempty"`
// Render subchart notes if enabled
SubNotes bool `protobuf:"varint,13,opt,name=subNotes" json:"subNotes,omitempty"`
} }
func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} }
@ -465,6 +467,13 @@ func (m *UpdateReleaseRequest) GetForce() bool {
return false return false
} }
func (m *UpdateReleaseRequest) GetSubNotes() bool {
if m != nil {
return m.SubNotes
}
return false
}
func (m *UpdateReleaseRequest) GetDescription() string { func (m *UpdateReleaseRequest) GetDescription() string {
if m != nil { if m != nil {
return m.Description return m.Description
@ -624,6 +633,7 @@ type InstallReleaseRequest struct {
DisableCrdHook bool `protobuf:"varint,10,opt,name=disable_crd_hook,json=disableCrdHook" json:"disable_crd_hook,omitempty"` DisableCrdHook bool `protobuf:"varint,10,opt,name=disable_crd_hook,json=disableCrdHook" json:"disable_crd_hook,omitempty"`
// Description, if set, will set the description for the installed release // Description, if set, will set the description for the installed release
Description string `protobuf:"bytes,11,opt,name=description" json:"description,omitempty"` Description string `protobuf:"bytes,11,opt,name=description" json:"description,omitempty"`
SubNotes bool `protobuf:"varint,12,opt,name=subNotes" json:"subNotes,omitempty"`
} }
func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} }
@ -701,6 +711,13 @@ func (m *InstallReleaseRequest) GetDisableCrdHook() bool {
return false return false
} }
func (m *InstallReleaseRequest) GetSubNotes() bool {
if m != nil {
return m.SubNotes
}
return false
}
func (m *InstallReleaseRequest) GetDescription() string { func (m *InstallReleaseRequest) GetDescription() string {
if m != nil { if m != nil {
return m.Description return m.Description

@ -148,7 +148,7 @@ func (s *Server) URL() string {
return s.srv.URL return s.srv.URL
} }
// LinkIndices links the index created with CreateIndex and makes a symboic link to the repositories/cache directory. // LinkIndices links the index created with CreateIndex and makes a symbolic link to the repositories/cache directory.
// //
// This makes it possible to simulate a local cache of a repository. // This makes it possible to simulate a local cache of a repository.
func (s *Server) LinkIndices() error { func (s *Server) LinkIndices() error {

@ -45,7 +45,7 @@ type Secrets struct {
Log func(string, ...interface{}) Log func(string, ...interface{})
} }
// NewSecrets initializes a new Secrets wrapping an implmenetation of // NewSecrets initializes a new Secrets wrapping an implementation of
// the kubernetes SecretsInterface. // the kubernetes SecretsInterface.
func NewSecrets(impl corev1.SecretInterface) *Secrets { func NewSecrets(impl corev1.SecretInterface) *Secrets {
return &Secrets{ return &Secrets{

@ -393,6 +393,10 @@ func typedVal(v []rune, st bool) interface{} {
return nil return nil
} }
if strings.EqualFold(val, "0") {
return int64(0)
}
// If this value does not start with zero, try parsing it to an int // If this value does not start with zero, try parsing it to an int
if len(val) != 0 && val[0] != '0' { if len(val) != 0 && val[0] != '0' {
if iv, err := strconv.ParseInt(val, 10, 64); err == nil { if iv, err := strconv.ParseInt(val, 10, 64); err == nil {

@ -85,6 +85,11 @@ func TestParseSet(t *testing.T) {
expect: map[string]interface{}{"is_null": "null"}, expect: map[string]interface{}{"is_null": "null"},
err: false, err: false,
}, },
{
str: "zero=0",
expect: map[string]interface{}{"zero": "0"},
err: false,
},
} }
tests := []struct { tests := []struct {
str string str string
@ -123,6 +128,10 @@ func TestParseSet(t *testing.T) {
str: "leading_zeros=00009", str: "leading_zeros=00009",
expect: map[string]interface{}{"leading_zeros": "00009"}, expect: map[string]interface{}{"leading_zeros": "00009"},
}, },
{
str: "zero_int=0",
expect: map[string]interface{}{"zero_int": 0},
},
{ {
str: "long_int=1234567890", str: "long_int=1234567890",
expect: map[string]interface{}{"long_int": 1234567890}, expect: map[string]interface{}{"long_int": 1234567890},

@ -119,7 +119,7 @@ type KubeClient interface {
// by "\n---\n"). // by "\n---\n").
Delete(namespace string, reader io.Reader) error Delete(namespace string, reader io.Reader) error
// Watch the resource in reader until it is "ready". // WatchUntilReady watch the resource in reader until it is "ready".
// //
// For Jobs, "ready" means the job ran to completion (excited without error). // For Jobs, "ready" means the job ran to completion (excited without error).
// For all other kinds, it means the kind was created or modified without // For all other kinds, it means the kind was created or modified without

@ -124,14 +124,16 @@ func (k *kindSorter) Less(i, j int) bool {
b := k.manifests[j] b := k.manifests[j]
first, aok := k.ordering[a.Head.Kind] first, aok := k.ordering[a.Head.Kind]
second, bok := k.ordering[b.Head.Kind] second, bok := k.ordering[b.Head.Kind]
// if same kind (including unknown) sub sort alphanumeric
if first == second { if !aok && !bok {
// if both are unknown and of different kind sort by kind alphabetically // if both are unknown then sort alphabetically by kind and name
if !aok && !bok && a.Head.Kind != b.Head.Kind { if a.Head.Kind != b.Head.Kind {
return a.Head.Kind < b.Head.Kind return a.Head.Kind < b.Head.Kind
} else {
return a.Name < b.Name
} }
return a.Name < b.Name
} }
// unknown kind is last // unknown kind is last
if !aok { if !aok {
return false return false
@ -139,6 +141,11 @@ func (k *kindSorter) Less(i, j int) bool {
if !bok { if !bok {
return true return true
} }
// if same kind sub sort alphanumeric
if first == second {
return a.Name < b.Name
}
// sort different kinds // sort different kinds
return first < second return first < second
} }

@ -223,3 +223,24 @@ func TestKindSorterSubSort(t *testing.T) {
}) })
} }
} }
func TestKindSorterNamespaceAgainstUnknown(t *testing.T) {
unknown := Manifest{
Name: "a",
Head: &util.SimpleHead{Kind: "Unknown"},
}
namespace := Manifest{
Name: "b",
Head: &util.SimpleHead{Kind: "Namespace"},
}
manifests := []Manifest{unknown, namespace}
sortByKind(manifests, InstallOrder)
expectedOrder := []Manifest{namespace, unknown}
for i, manifest := range manifests {
if expectedOrder[i].Name != manifest.Name {
t.Errorf("Expected %s, got %s", expectedOrder[i].Name, manifest.Name)
}
}
}

@ -84,7 +84,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return nil, err return nil, err
} }
hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions)
if err != nil { if err != nil {
// Return a release with partial data so that client can show debugging // Return a release with partial data so that client can show debugging
// information. // information.

@ -268,7 +268,7 @@ func TestInstallRelease_WrongTillerVersion(t *testing.T) {
} }
} }
func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
@ -300,6 +300,39 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
} }
} }
func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
req := installRequest(withSubNotes(),
withChart(
withNotes(notesText),
withDependency(withNotes(notesText+" child")),
))
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
t.Logf("rel: %v", rel)
if !strings.Contains(rel.Info.Status.Notes, notesText) || !strings.Contains(rel.Info.Status.Notes, notesText+" child") {
t.Fatalf("Expected '%s', got '%s'", notesText+"\n"+notesText+" child", rel.Info.Status.Notes)
}
if rel.Info.Description != "Install complete" {
t.Errorf("unexpected description: %s", rel.Info.Description)
}
}
func TestInstallRelease_DryRun(t *testing.T) { func TestInstallRelease_DryRun(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()

@ -140,7 +140,7 @@ func (s *ReleaseServer) partition(rels []*release.Release, cap int) <-chan []*re
// Over-cap, push chunk onto channel to send over gRPC stream // Over-cap, push chunk onto channel to send over gRPC stream
s.Log("partitioned at %d with %d releases (cap=%d)", fill, len(chunk), cap) s.Log("partitioned at %d with %d releases (cap=%d)", fill, len(chunk), cap)
chunks <- chunk chunks <- chunk
// reset paritioning state // reset partitioning state
chunk = nil chunk = nil
fill = 0 fill = 0
} }

@ -278,7 +278,7 @@ func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet
return chartutil.NewVersionSet(versions...), nil return chartutil.NewVersionSet(versions...), nil
} }
func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) { func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, subNotes bool, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) {
// Guard to make sure Tiller is at the right version to handle this chart. // Guard to make sure Tiller is at the right version to handle this chart.
sver := version.GetVersion() sver := version.GetVersion()
if ch.Metadata.TillerVersion != "" && if ch.Metadata.TillerVersion != "" &&
@ -307,18 +307,23 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// text file. We have to spin through this map because the file contains path information, so we // text file. We have to spin through this map because the file contains path information, so we
// look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip
// it in the sortHooks. // it in the sortHooks.
notes := "" var notesBuffer bytes.Buffer
for k, v := range files { for k, v := range files {
if strings.HasSuffix(k, notesFileSuffix) { if strings.HasSuffix(k, notesFileSuffix) {
// Only apply the notes if it belongs to the parent chart if subNotes || (k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix)) {
// Note: Do not use filePath.Join since it creates a path with \ which is not expected
if k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix) { // If buffer contains data, add newline before adding more
notes = v if notesBuffer.Len() > 0 {
notesBuffer.WriteString("\n")
}
notesBuffer.WriteString(v)
} }
delete(files, k) delete(files, k)
} }
} }
notes := notesBuffer.String()
// Sort hooks, manifests, and partials. Only hooks and manifests are returned, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also // as partials are not used after renderer.Render. Empty manifests are also
// removed here. // removed here.
@ -451,7 +456,7 @@ func (s *ReleaseServer) deleteHookByPolicy(h *release.Hook, policy string, name,
return nil return nil
} }
// hookShouldBeDeleted determines whether the defined hook deletion policy matches the hook deletion polices // hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted. // supported by helm. If so, mark the hook as one should be deleted.
func hookHasDeletePolicy(h *release.Hook, policy string) bool { func hookHasDeletePolicy(h *release.Hook, policy string) bool {
if dp, ok := deletePolices[policy]; ok { if dp, ok := deletePolices[policy]; ok {

@ -221,6 +221,12 @@ func withChart(chartOpts ...chartOption) installOption {
} }
} }
func withSubNotes() installOption {
return func(opts *installOptions) {
opts.SubNotes = true
}
}
func installRequest(opts ...installOption) *services.InstallReleaseRequest { func installRequest(opts ...installOption) *services.InstallReleaseRequest {
reqOpts := &installOptions{ reqOpts := &installOptions{
&services.InstallReleaseRequest{ &services.InstallReleaseRequest{

@ -113,7 +113,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
return nil, nil, err return nil, nil, err
} }
hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

@ -24,15 +24,6 @@ import (
"k8s.io/helm/pkg/tiller/environment" "k8s.io/helm/pkg/tiller/environment"
) )
// resourcePolicyAnno is the annotation name for a resource policy
const resourcePolicyAnno = "helm.sh/resource-policy"
// keepPolicy is the resource policy type for keep
//
// This resource policy type allows resources to skip being deleted
// during an uninstallRelease action.
const keepPolicy = "keep"
func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) { func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) {
remaining := []Manifest{} remaining := []Manifest{}
keep := []Manifest{} keep := []Manifest{}
@ -43,14 +34,14 @@ func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) {
continue continue
} }
resourcePolicyType, ok := m.Head.Metadata.Annotations[resourcePolicyAnno] resourcePolicyType, ok := m.Head.Metadata.Annotations[kube.ResourcePolicyAnno]
if !ok { if !ok {
remaining = append(remaining, m) remaining = append(remaining, m)
continue continue
} }
resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType)) resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType))
if resourcePolicyType == keepPolicy { if resourcePolicyType == kube.KeepPolicy {
keep = append(keep, m) keep = append(keep, m)
} }

@ -73,7 +73,7 @@ func ExtractHostname(addr string) (string, error) {
return stripPort(u.Host), nil return stripPort(u.Host), nil
} }
// Backported from Go 1.8 because Circle is still on 1.7 // stripPort from Go 1.8 because Circle is still on 1.7
func stripPort(hostport string) string { func stripPort(hostport string) string {
colon := strings.IndexByte(hostport, ':') colon := strings.IndexByte(hostport, ':')
if colon == -1 { if colon == -1 {

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

@ -22,6 +22,6 @@ COPY helm /helm
COPY tiller /tiller COPY tiller /tiller
EXPOSE 44134 EXPOSE 44134
USER nobody USER 65534
ENTRYPOINT ["/tiller"] ENTRYPOINT ["/tiller"]

@ -21,6 +21,6 @@ ENV HOME /tmp
COPY tiller /tiller COPY tiller /tiller
EXPOSE 44134 EXPOSE 44134
USER nobody USER 65534
ENTRYPOINT ["/tiller", "--experimental-release"] ENTRYPOINT ["/tiller", "--experimental-release"]

Loading…
Cancel
Save