Merge branch 'master' into master

pull/2862/head
rprobotics 8 years ago committed by GitHub
commit 746b312e20

@ -0,0 +1,25 @@
version: 2
jobs:
build:
working_directory: /go/src/k8s.io/helm
parallelism: 3
docker:
- image: golang:1.8
environment:
PROJECT_NAME: "kubernetes-helm"
steps:
- checkout
- setup_remote_docker
- run:
name: install dependencies
command: make bootstrap
- save_cache:
key: vendor-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }}
paths:
- vendor
- run:
name: test
command: .circleci/test.sh
- deploy:
name: deploy
command: .circleci/deploy.sh

@ -15,6 +15,11 @@
# limitations under the License. # limitations under the License.
set -euo pipefail set -euo pipefail
# Skip on pull request builds
if [[ -n "${CIRCLE_PR_NUMBER:-}" ]]; then
exit
fi
: ${GCLOUD_SERVICE_KEY:?"GCLOUD_SERVICE_KEY environment variable is not set"} : ${GCLOUD_SERVICE_KEY:?"GCLOUD_SERVICE_KEY environment variable is not set"}
: ${PROJECT_NAME:?"PROJECT_NAME environment variable is not set"} : ${PROJECT_NAME:?"PROJECT_NAME environment variable is not set"}
@ -27,13 +32,21 @@ else
exit 1 exit 1
fi fi
echo "Updating gcloud components" echo "Install docker client"
sudo /opt/google-cloud-sdk/bin/gcloud --quiet components update VER="17.03.0-ce"
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
echo "Install gcloud components"
export CLOUDSDK_CORE_DISABLE_PROMPTS=1
curl https://sdk.cloud.google.com | bash
${HOME}/google-cloud-sdk/bin/gcloud --quiet components update
echo "Configuring gcloud authentication" echo "Configuring gcloud authentication"
echo "${GCLOUD_SERVICE_KEY}" | base64 --decode > "${HOME}/gcloud-service-key.json" echo "${GCLOUD_SERVICE_KEY}" | base64 --decode > "${HOME}/gcloud-service-key.json"
sudo /opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file "${HOME}/gcloud-service-key.json" ${HOME}/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file "${HOME}/gcloud-service-key.json"
sudo /opt/google-cloud-sdk/bin/gcloud config set project "${PROJECT_NAME}" ${HOME}/google-cloud-sdk/bin/gcloud config set project "${PROJECT_NAME}"
docker login -e 1234@5678.com -u _json_key -p "$(cat ${HOME}/gcloud-service-key.json)" https://gcr.io docker login -e 1234@5678.com -u _json_key -p "$(cat ${HOME}/gcloud-service-key.json)" https://gcr.io
echo "Building the tiller image" echo "Building the tiller image"
@ -47,4 +60,4 @@ make build-cross
make dist checksum VERSION="${VERSION}" make dist checksum VERSION="${VERSION}"
echo "Pushing binaries to gs bucket" echo "Pushing binaries to gs bucket"
sudo /opt/google-cloud-sdk/bin/gsutil cp ./_dist/* "gs://${PROJECT_NAME}" ${HOME}/google-cloud-sdk/bin/gsutil cp ./_dist/* "gs://${PROJECT_NAME}"

@ -36,6 +36,7 @@ Whether you are a user or contributor, official support channels include:
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.
## Milestones ## Milestones
We use milestones to track progress of releases. There are also 2 special milestones We use milestones to track progress of releases. There are also 2 special milestones
used for helping us keep work organized: `Upcoming - Minor` and `Upcoming - Major` used for helping us keep work organized: `Upcoming - Minor` and `Upcoming - Major`
@ -52,10 +53,27 @@ An issue that we are not sure we will be doing will not be added to any mileston
A milestone (and hence release) is considered done when all outstanding issues/PRs have been closed or moved to another milestone. A milestone (and hence release) is considered done when all outstanding issues/PRs have been closed or moved to another milestone.
## Semver
Helm maintains a strong commitment to backward compatibility. All of our changes to protocols and formats are backward compatible from Helm 2.0 until Helm 3.0. No features, flags, or commands are removed or substantially modified (other than bug fixes).
We also try very hard to not change publicly accessible Go library definitions inside of the `pkg/` directory of our source code.
For a quick summary of our backward compatibility guidelines for releases between 2.0 and 3.0:
- Protobuf and gRPC changes MUST be backward compatible.
- Command line commands, flags, and arguments MUST be backward compatible
- File formats (such as Chart.yaml, repositories.yaml, and requirements.yaml) MUST be backward compatible
- Any chart that worked on a previous version of Helm MUST work on a new version of Helm (barring the cases where (a) Kubernetes itself changed, and (b) the chart worked because it exploited a bug)
- Chart repository functionality MUST be backward compatible
- Go libraries inside of `pkg/` SHOULD remain backward compatible (though code inside of `cmd/` may be changed from release to release without notice).
## Issues ## Issues
Issues are used as the primary method for tracking anything to do with the Helm project. Issues are used as the primary method for tracking anything to do with the Helm project.
### Issue Types ### Issue Types
There are 4 types of issues (each with their own corresponding [label](#labels)): There are 4 types of issues (each with their own corresponding [label](#labels)):
- Question: These are support or functionality inquiries that we want to have a record of for - Question: These are support or functionality inquiries that we want to have a record of for
future reference. Generally these are questions that are too complex or large to store in the future reference. Generally these are questions that are too complex or large to store in the
@ -72,6 +90,7 @@ from a "Proposal" or can be submitted individually depending on the size.
- Bugs: These track bugs with the code or problems with the documentation (i.e. missing or incomplete) - Bugs: These track bugs with the code or problems with the documentation (i.e. missing or incomplete)
### Issue Lifecycle ### Issue Lifecycle
The issue lifecycle is mainly driven by the core maintainers, but is good information for those The issue lifecycle is mainly driven by the core maintainers, but is good information for those
contributing to Helm. All issue types follow the same general lifecycle. Differences are noted below. contributing to Helm. All issue types follow the same general lifecycle. Differences are noted below.
1. Issue creation 1. Issue creation
@ -107,9 +126,11 @@ https://github.com/kubernetes/helm/blob/master/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 PRs
## 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 to track code changes
### PR Lifecycle ### PR Lifecycle
1. PR creation 1. PR creation
- 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,
@ -149,19 +170,23 @@ Like any good open source project, we use Pull Requests to track code changes
merge the PR once it is approved. merge the PR once it is approved.
#### Documentation PRs #### Documentation PRs
Documentation PRs will follow the same lifecycle as other PRs. They will also be labeled with the Documentation PRs will follow the same lifecycle as other PRs. They will also be labeled with the
`docs` label. For documentation, special attention will be paid to spelling, grammar, and clarity `docs` label. For documentation, special attention will be paid to spelling, grammar, and clarity
(whereas those things don't matter *as* much for comments in code). (whereas those things don't matter *as* much for comments in code).
## The Triager ## The Triager
Each week, one of the core maintainers will serve as the designated "triager" starting after the Each week, one of the core maintainers will serve as the designated "triager" starting after the
public standup meetings on Thursday. This person will be in charge triaging new PRs and issues public standup meetings on Thursday. This person will be in charge triaging new PRs and issues
throughout the work week. throughout the work week.
## Labels ## Labels
The following tables define all label types used for Helm. It is split up by category. The following tables define all label types used for Helm. It is split up by category.
### Common ### Common
| Label | Description | | Label | Description |
| ----- | ----------- | | ----- | ----------- |
| `bug` | Marks an issue as a bug or a PR as a bugfix | | `bug` | Marks an issue as a bug or a PR as a bugfix |
@ -173,6 +198,7 @@ The following tables define all label types used for Helm. It is split up by cat
| `refactor` | Indicates that the issue is a code refactor and is not fixing a bug or adding additional functionality | | `refactor` | Indicates that the issue is a code refactor and is not fixing a bug or adding additional functionality |
### Issue Specific ### Issue Specific
| Label | Description | | Label | Description |
| ----- | ----------- | | ----- | ----------- |
| `help wanted` | This issue is one the core maintainers cannot get to right now and would appreciate help with | | `help wanted` | This issue is one the core maintainers cannot get to right now and would appreciate help with |
@ -182,6 +208,7 @@ The following tables define all label types used for Helm. It is split up by cat
| `wont fix` | The issue has been discussed and will not be implemented (or accepted in the case of a proposal) | | `wont fix` | The issue has been discussed and will not be implemented (or accepted in the case of a proposal) |
### PR Specific ### PR Specific
| Label | Description | | Label | Description |
| ----- | ----------- | | ----- | ----------- |
| `awaiting review` | The PR has been triaged and is ready for someone to review | | `awaiting review` | The PR has been triaged and is ready for someone to review |
@ -194,6 +221,7 @@ The following tables define all label types used for Helm. It is split up by cat
| `picked` | This PR has been picked into a feature branch | | `picked` | This PR has been picked into a feature branch |
#### Size labels #### Size labels
Size labels are used to indicate how "dangerous" a PR is. The guidelines below are used to assign the Size labels are used to indicate how "dangerous" a PR is. The guidelines below are used to assign the
labels, but ultimately this can be changed by the maintainers. For example, even if a PR only makes labels, but ultimately this can be changed by the maintainers. For example, even if a PR only makes
30 lines of changes in 1 file, but it changes key functionality, it will likely be labeled as `size/large` 30 lines of changes in 1 file, but it changes key functionality, it will likely be labeled as `size/large`

@ -31,7 +31,7 @@ build:
.PHONY: build-cross .PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static" build-cross: LDFLAGS += -extldflags "-static"
build-cross: build-cross:
CGO_ENABLED=0 gox -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP) CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP)
.PHONY: dist .PHONY: dist
dist: dist:

@ -34,9 +34,10 @@ Think of it like apt/yum/homebrew for Kubernetes.
Binary downloads of the Helm client can be found at the following links: Binary downloads of the Helm client can be found at the following links:
- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.0-darwin-amd64.tar.gz) - [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-darwin-amd64.tar.gz)
- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.0-linux-amd64.tar.gz) - [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-amd64.tar.gz)
- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.0-linux-386.tar.gz) - [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-386.tar.gz)
- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-windows-amd64.tar.gz)
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!
macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`. macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`.

@ -35,6 +35,10 @@ message Hook {
RELEASE_TEST_SUCCESS = 9; RELEASE_TEST_SUCCESS = 9;
RELEASE_TEST_FAILURE = 10; RELEASE_TEST_FAILURE = 10;
} }
enum DeletePolicy {
SUCCEEDED = 0;
FAILED = 1;
}
string name = 1; string name = 1;
// Kind is the Kubernetes kind. // Kind is the Kubernetes kind.
string kind = 2; string kind = 2;
@ -48,4 +52,6 @@ message Hook {
google.protobuf.Timestamp last_run = 6; google.protobuf.Timestamp last_run = 6;
// Weight indicates the sort order for execution among similar Hook type // Weight indicates the sort order for execution among similar Hook type
int32 weight = 7; int32 weight = 7;
// DeletePolicies are the policies that indicate when to delete the hook
repeated DeletePolicy delete_policies = 8;
} }

@ -1,52 +0,0 @@
machine:
pre:
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
environment:
GOVERSION: "1.8.3"
GOPATH: "${HOME}/.go_workspace"
WORKDIR: "${GOPATH}/src/k8s.io/helm"
PROJECT_NAME: "kubernetes-helm"
services:
- docker
dependencies:
cache_directories:
- "~/.glide"
pre:
# remove old go files
- sudo rm -rf /usr/local/go
- rm -rf "$GOPATH"
override:
# install go
- wget "https://storage.googleapis.com/golang/go${GOVERSION}.linux-amd64.tar.gz" -O "${HOME}/go${GOVERSION}.tar.gz"
- sudo tar -C /usr/local -xzf "${HOME}/go${GOVERSION}.tar.gz"
# move repository to the canonical import path
- mkdir -p "$(dirname ${WORKDIR})"
- cp -R "${HOME}/helm" "${WORKDIR}"
# install dependencies
- cd "${WORKDIR}" && make bootstrap
post:
- go env
test:
override:
- cd "${WORKDIR}" && ./scripts/ci.sh:
parallel: true
deployment:
release:
tag: /.*/
commands:
- cd "${WORKDIR}" && ./scripts/ci/deploy.sh
canary:
branch: master
commands:
- cd "${WORKDIR}" && ./scripts/ci/deploy.sh

@ -69,7 +69,7 @@ Environment:
$HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm $HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm
$HELM_HOST set an alternative Tiller host. The format is host:port $HELM_HOST set an alternative Tiller host. The format is host:port
$HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. $HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
$TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-namespace") $TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-system")
$KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config")
` `
@ -125,6 +125,7 @@ func newRootCmd(args []string) *cobra.Command {
newHomeCmd(out), newHomeCmd(out),
newInitCmd(out), newInitCmd(out),
newPluginCmd(out), newPluginCmd(out),
newTemplateCmd(out),
// Hidden documentation generator command: 'helm docs' // Hidden documentation generator command: 'helm docs'
newDocsCmd(out), newDocsCmd(out),

@ -299,7 +299,7 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) err
if err != nil { if err != nil {
return err return err
} }
lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local")) lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out)
if err != nil { if err != nil {
return err return err
} }
@ -314,7 +314,9 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) err
return nil return nil
} }
func initStableRepo(cacheFile string, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) {
func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmPath.Home) (*repo.Entry, error) {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL)
c := repo.Entry{ c := repo.Entry{
Name: stableRepository, Name: stableRepository,
URL: stableRepositoryURL, URL: stableRepositoryURL,
@ -338,8 +340,9 @@ func initStableRepo(cacheFile string, skipRefresh bool, home helmpath.Home) (*re
return &c, nil return &c, nil
} }
func initLocalRepo(indexFile, cacheFile string) (*repo.Entry, error) { func initLocalRepo(indexFile, cacheFile string, out io.Writer) (*repo.Entry, error) {
if fi, err := os.Stat(indexFile); err != nil { if fi, err := os.Stat(indexFile); err != nil {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL)
i := repo.NewIndexFile() i := repo.NewIndexFile()
if err := i.WriteFile(indexFile, 0644); err != nil { if err := i.WriteFile(indexFile, 0644); err != nil {
return nil, err return nil, err

@ -177,7 +177,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you")
f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into") f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")

@ -0,0 +1,236 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/tiller"
"k8s.io/helm/pkg/timeconv"
)
const templateDesc = `
Render chart templates locally and display the output.
This does not require Tiller. However, any values that would normally be
looked up or retrieved in-cluster will be faked locally. Additionally, none
of the server-side testing of chart validity (e.g. whether an API is supported)
is done.
To render just one template in a chart, use '-x':
$ helm template mychart -x templates/deployment.yaml
`
type templateCmd struct {
namespace string
valueFiles valueFiles
chartPath string
out io.Writer
client helm.Interface
values []string
nameTemplate string
showNotes bool
releaseName string
renderFiles []string
}
func newTemplateCmd(out io.Writer) *cobra.Command {
t := &templateCmd{
out: out,
}
cmd := &cobra.Command{
Use: "template [flags] CHART",
Short: fmt.Sprintf("locally render templates"),
Long: templateDesc,
RunE: t.run,
}
f := cmd.Flags()
f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&t.releaseName, "name", "n", "RELEASE-NAME", "release name")
f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into")
f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release")
return cmd
}
func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("chart is required")
}
// verify chart path exists
if _, err := os.Stat(args[0]); err == nil {
if t.chartPath, err = filepath.Abs(args[0]); err != nil {
return err
}
} else {
return err
}
// verify specified templates exist relative to chart
rf := []string{}
var af string
var err error
if len(t.renderFiles) > 0 {
for _, f := range t.renderFiles {
if !filepath.IsAbs(f) {
af, err = filepath.Abs(t.chartPath + "/" + f)
if err != nil {
return fmt.Errorf("could not resolve template path: %s", err)
}
} else {
af = f
}
rf = append(rf, af)
if _, err := os.Stat(af); err != nil {
return fmt.Errorf("could not resolve template path: %s", err)
}
}
}
if t.namespace == "" {
t.namespace = defaultNamespace()
}
// get combined values and create config
rawVals, err := vals(t.valueFiles, t.values)
if err != nil {
return err
}
config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}}
// If template is specified, try to run the template.
if t.nameTemplate != "" {
t.releaseName, err = generateName(t.nameTemplate)
if err != nil {
return err
}
}
// Check chart requirements to make sure all dependencies are present in /charts
c, err := chartutil.Load(t.chartPath)
if err != nil {
return prettyError(err)
}
if req, err := chartutil.LoadRequirements(c); err == nil {
if err := checkDependencies(c, req); err != nil {
return prettyError(err)
}
} else if err != chartutil.ErrRequirementsNotFound {
return fmt.Errorf("cannot load requirements: %v", err)
}
options := chartutil.ReleaseOptions{
Name: t.releaseName,
Time: timeconv.Now(),
Namespace: t.namespace,
}
err = chartutil.ProcessRequirementsEnabled(c, config)
if err != nil {
return err
}
err = chartutil.ProcessRequirementsImportValues(c)
if err != nil {
return err
}
// Set up engine.
renderer := engine.New()
vals, err := chartutil.ToRenderValues(c, config, options)
if err != nil {
return err
}
out, err := renderer.Render(c, vals)
listManifests := []tiller.Manifest{}
if err != nil {
return err
}
// extract kind and name
re := regexp.MustCompile("kind:(.*)\n")
for k, v := range out {
match := re.FindStringSubmatch(v)
h := "Unknown"
if len(match) == 2 {
h = strings.TrimSpace(match[1])
}
m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}}
listManifests = append(listManifests, m)
}
in := func(needle string, haystack []string) bool {
// make needle path absolute
d := strings.Split(needle, "/")
dd := d[1:]
an := t.chartPath + "/" + strings.Join(dd, "/")
for _, h := range haystack {
if h == an {
return true
}
}
return false
}
if settings.Debug {
rel := &release.Release{
Name: t.releaseName,
Chart: c,
Config: config,
Version: 1,
Namespace: t.namespace,
Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())},
}
printRelease(os.Stdout, rel)
}
for _, m := range tiller.SortByKind(listManifests) {
if len(t.renderFiles) > 0 && in(m.Name, rf) == false {
continue
}
data := m.Content
b := filepath.Base(m.Name)
if !t.showNotes && b == "NOTES.txt" {
continue
}
if strings.HasPrefix(b, "_") {
continue
}
fmt.Printf("---\n# Source: %s\n", m.Name)
fmt.Println(data)
}
return nil
}

@ -0,0 +1,160 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
)
var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1"
func TestTemplateCmd(t *testing.T) {
absChartPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
desc string
args []string
expectKey string
expectValue string
}{
{
name: "check_name",
desc: "check for a known name in chart",
args: []string{chartPath},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: nginx",
},
{
name: "check_set_name",
desc: "verify --set values exist",
args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute",
desc: "verify --execute single template",
args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute_absolute",
desc: "verify --execute single template",
args: []string{chartPath, "-x", absChartPath + "/" + "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_namespace",
desc: "verify --namespace",
args: []string{chartPath, "--namespace", "test"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "namespace: \"test\"",
},
{
name: "check_release_name",
desc: "verify --release exists",
args: []string{chartPath, "--name", "test"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-name: \"test\"",
},
{
name: "check_notes",
desc: "verify --notes shows notes",
args: []string{chartPath, "--notes", "true"},
expectKey: "subchart1/templates/NOTES.txt",
expectValue: "Sample notes for subchart1",
},
{
name: "check_values_files",
desc: "verify --values files values exist",
args: []string{chartPath, "--values", chartPath + "/charts/subchartA/values.yaml"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "name: apache",
},
{
name: "check_name_template",
desc: "verify --name-template result exists",
args: []string{chartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-name: \"foobar-YWJj-baz\"",
},
}
var buf bytes.Buffer
for _, tt := range tests {
t.Run(tt.name, func(T *testing.T) {
// capture stdout
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
// execute template command
out := bytes.NewBuffer(nil)
cmd := newTemplateCmd(out)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if err != nil {
t.Errorf("expected: %v, got %v", tt.expectValue, err)
}
// restore stdout
w.Close()
os.Stdout = old
var b bytes.Buffer
io.Copy(&b, r)
r.Close()
// scan yaml into map[<path>]yaml
scanner := bufio.NewScanner(&b)
next := false
lastKey := ""
m := map[string]string{}
for scanner.Scan() {
if scanner.Text() == "---" {
next = true
} else if next {
// remove '# Source: '
head := "# Source: "
lastKey = scanner.Text()[len(head):]
next = false
} else {
m[lastKey] = m[lastKey] + scanner.Text() + "\n"
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
if v, ok := m[tt.expectKey]; ok {
if strings.Contains(v, tt.expectValue) == false {
t.Errorf("failed to match expected value %s in %s", tt.expectValue, v)
}
} else {
t.Errorf("could not find key %s", tt.expectKey)
}
buf.Reset()
})
}
}

@ -121,7 +121,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading") f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading")
f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "path to the keyring that contains public signing keys") f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "path to the keyring that contains public signing keys")
f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install") f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.StringVar(&upgrade.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)") f.StringVar(&upgrade.namespace, "namespace", "", "namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace")
f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used")
f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")

@ -138,7 +138,13 @@ data:
food: "PIZZA" food: "PIZZA"
``` ```
It is considered good (almost mandatory) practice to set defaults with `default` for any object that originates from `.Values`. (In some places, an `if` conditional guard may be better suited. We'll see those in the next section.) In an actual chart, all static default values should live in the values.yaml, and should not be repeated using the `default` command (otherwise they would be redundant). However, the `default` command is perfect for computed values, which can not be declared inside values.yaml. For example:
```yaml
drink: {{ .Values.favorite.drink | default (printf "%s-tea" (include "fullname" .)) }}
```
In some places, an `if` conditional guard may be better suited than `default`. We'll see those in the next section.
Template functions and pipelines are a powerful way to transform information and then insert it into your YAML. But sometimes it's necessary to add some template logic that is a little more sophisticated than just inserting a string. In the next section we will look at the control structures provided by the template language. Template functions and pipelines are a powerful way to transform information and then insert it into your YAML. But sometimes it's necessary to add some template logic that is a little more sophisticated than just inserting a string. In the next section we will look at the control structures provided by the template language.

@ -87,7 +87,7 @@ in the future.) It is considered good practice to add a hook weight, and set it
to `0` if weight is not important. to `0` if weight is not important.
### Hook resources are unmanaged ### Hook resources are not managed with correponding releases
The resources that a hook creates are not tracked or managed as part of the The resources that a hook creates are not tracked or managed as part of the
release. Once Tiller verifies that the hook has reached its ready state, it release. Once Tiller verifies that the hook has reached its ready state, it
@ -95,8 +95,8 @@ will leave the hook resource alone.
Practically speaking, this means that if you create resources in a hook, you Practically speaking, this means that if you create resources in a hook, you
cannot rely upon `helm delete` to remove the resources. To destroy such cannot rely upon `helm delete` to remove the resources. To destroy such
resources, you need to write code to perform this operation in a `pre-delete` resources, you need to either write code to perform this operation in a `pre-delete`
or `post-delete` hook. or `post-delete` hook or add `"helm.sh/hook-delete-policy"` annotation to the hook template file.
## Writing a Hook ## Writing a Hook
@ -122,6 +122,7 @@ metadata:
# job is considered part of the release. # job is considered part of the release.
"helm.sh/hook": post-install "helm.sh/hook": post-install
"helm.sh/hook-weight": "-5" "helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec: spec:
template: template:
metadata: metadata:
@ -160,7 +161,7 @@ and a config map as a pre-install hook.
When subcharts declare hooks, those are also evaluated. There is no way When subcharts declare hooks, those are also evaluated. There is no way
for a top-level chart to disable the hooks declared by subcharts. for a top-level chart to disable the hooks declared by subcharts.
It is also possible to define a weight for a hook which will help build a It is possible to define a weight for a hook which will help build a
deterministic executing order. Weights are defined using the following annotation: deterministic executing order. Weights are defined using the following annotation:
``` ```
@ -172,3 +173,12 @@ Hook weights can be positive or negative numbers but must be represented as
strings. When Tiller starts the execution cycle of hooks of a particular Kind it strings. When Tiller starts the execution cycle of hooks of a particular Kind it
will sort those hooks in ascending order. will sort those hooks in ascending order.
It is also possible to define policies that determine when to delete corresponding hook resources. Hook deletion policies are defined using the following annotation:
```
annotations:
"helm.sh/hook-delete-policy": hook-succeeded
```
When using `"helm.sh/hook-delete-policy"` annoation, you can choose its value from `"hook-succeeded"` and `"hook-failed"`. The value `"hook-succeeded"` specifies Tiller should delete the hook after the hook is successfully excuted, while the value `"hook-failed"`specifies Tiller should delete the hook if the hook is failed during execuation.

@ -25,7 +25,7 @@ Environment:
$HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm $HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm
$HELM_HOST set an alternative Tiller host. The format is host:port $HELM_HOST set an alternative Tiller host. The format is host:port
$HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. $HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
$TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-namespace") $TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-system")
$KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config")
@ -61,9 +61,10 @@ Environment:
* [helm search](helm_search.md) - search for a keyword in charts * [helm search](helm_search.md) - search for a keyword in charts
* [helm serve](helm_serve.md) - start a local http web server * [helm serve](helm_serve.md) - start a local http web server
* [helm status](helm_status.md) - displays the status of the named release * [helm status](helm_status.md) - displays the status of the named release
* [helm template](helm_template.md) - locally render templates
* [helm test](helm_test.md) - test a release * [helm test](helm_test.md) - test a release
* [helm upgrade](helm_upgrade.md) - upgrade a release * [helm upgrade](helm_upgrade.md) - upgrade a release
* [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid * [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid
* [helm version](helm_version.md) - print the client/server version information * [helm version](helm_version.md) - print the client/server version information
###### Auto generated by spf13/cobra on 23-Jun-2017 ###### Auto generated by spf13/cobra on 8-Aug-2017

@ -76,7 +76,7 @@ helm install [CHART]
--keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg") --keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg")
-n, --name string release name. If unspecified, it will autogenerate one for you -n, --name string release name. If unspecified, it will autogenerate one for you
--name-template string specify template used to name the release --name-template string specify template used to name the release
--namespace string namespace to install the release into --namespace string namespace to install the release into. Defaults to the current kube config namespace.
--no-hooks prevent hooks from running during install --no-hooks prevent hooks from running during install
--replace re-use the given name, even if that name is already used. This is unsafe in production --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
@ -106,4 +106,4 @@ helm install [CHART]
### SEE ALSO ### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 23-Jun-2017 ###### Auto generated by spf13/cobra on 15-Aug-2017

@ -0,0 +1,50 @@
## helm template
locally render templates
### Synopsis
Render chart templates locally and display the output.
This does not require Tiller. However, any values that would normally be
looked up or retrieved in-cluster will be faked locally. Additionally, none
of the server-side testing of chart validity (e.g. whether an API is supported)
is done.
To render just one template in a chart, use '-x':
$ helm template mychart -x templates/deployment.yaml
```
helm template [flags] CHART
```
### Options
```
-x, --execute stringArray only execute the given templates
-n, --name string release name (default "RELEASE-NAME")
--name-template string specify template used to name the release
--namespace string namespace to install the release into
--notes show the computed NOTES.txt file as well
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default [])
```
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 24-Aug-2017

@ -44,7 +44,7 @@ helm upgrade [RELEASE] [CHART]
-i, --install if a release by this name doesn't already exist, run an install -i, --install if a release by this name doesn't already exist, run an install
--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 that contains public signing keys (default "~/.gnupg/pubring.gpg") --keyring string path to the keyring that contains public signing keys (default "~/.gnupg/pubring.gpg")
--namespace string namespace to install the release into (only used if --install is set) (default "default") --namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace
--no-hooks disable pre/post upgrade hooks --no-hooks disable pre/post upgrade hooks
--recreate-pods performs pods restart for the resource if applicable --recreate-pods performs pods restart for the resource if applicable
--repo string chart repository url where to locate the requested chart --repo string chart repository url where to locate the requested chart
@ -76,4 +76,4 @@ helm upgrade [RELEASE] [CHART]
### SEE ALSO ### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 23-Jun-2017 ###### Auto generated by spf13/cobra on 15-Aug-2017

@ -48,7 +48,7 @@ $ chmod 700 get_helm.sh
$ ./get_helm.sh $ ./get_helm.sh
``` ```
Yes, you can `curl ...| bash` that if you want to live on the edge. Yes, you can `curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash` that if you want to live on the edge.
### From Canary Builds ### From Canary Builds
@ -102,7 +102,7 @@ whatever cluster `kubectl` connects to by default (`kubectl config
view`). Once it connects, it will install `tiller` into the view`). Once it connects, it will install `tiller` into the
`kube-system` namespace. `kube-system` namespace.
After `helm init`, you should be able to run `kubectl get po --namespace After `helm init`, you should be able to run `kubectl get pods --namespace
kube-system` and see Tiller running. kube-system` and see Tiller running.
You can explicitly tell `helm init` to... You can explicitly tell `helm init` to...

@ -44,7 +44,7 @@ tag on their plugin repositories.
Tools layered on top of Helm or Tiller. Tools layered on top of Helm or Tiller.
- [Wheel](https://github.com/appscode/wheel) - Ajax friendly Helm Tiller Proxy using [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) - [AppsCode Swift](https://github.com/appscode/swift) - Ajax friendly Helm Tiller Proxy using [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)
- [Quay App Registry](https://coreos.com/blog/quay-application-registry-for-kubernetes.html) - Open Kubernetes application registry, including a Helm access client - [Quay App Registry](https://coreos.com/blog/quay-application-registry-for-kubernetes.html) - Open Kubernetes application registry, including a Helm access client
- [Chartify](https://github.com/appscode/chartify) - Generate Helm charts from existing Kubernetes resources. - [Chartify](https://github.com/appscode/chartify) - Generate Helm charts from existing Kubernetes resources.
- [VIM-Kubernetes](https://github.com/andrewstuart/vim-kubernetes) - VIM plugin for Kubernetes and Helm - [VIM-Kubernetes](https://github.com/andrewstuart/vim-kubernetes) - VIM plugin for Kubernetes and Helm

@ -221,7 +221,7 @@ const defaultNotes = `1. Get the application URL by running these commands:
{{- else if contains "ClusterIP" .Values.service.type }} {{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application" echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} kubectl port-forward $POD_NAME 8080:{{ .Values.service.internalPort }}
{{- end }} {{- end }}
` `

@ -115,6 +115,14 @@ func Save(c *chart.Chart, outDir string) (string, error) {
filename := fmt.Sprintf("%s-%s.tgz", cfile.Name, cfile.Version) filename := fmt.Sprintf("%s-%s.tgz", cfile.Name, cfile.Version)
filename = filepath.Join(outDir, filename) filename = filepath.Join(outDir, filename)
if stat, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(filename), 0755); !os.IsExist(err) {
return "", err
}
} else if !stat.IsDir() {
return "", fmt.Errorf("is not a directory: %s", filepath.Dir(filename))
}
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
return "", err return "", err

@ -3,7 +3,7 @@
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
# subchartA # subchartA
service: service:
name: nginx name: apache
type: ClusterIP type: ClusterIP
externalPort: 80 externalPort: 80
internalPort: 80 internalPort: 80

@ -0,0 +1 @@
Sample notes for {{ .Chart.Name }}

@ -4,6 +4,8 @@ metadata:
name: {{ .Chart.Name }} name: {{ .Chart.Name }}
labels: labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
namespace: "{{ .Release.Namespace }}"
release-name: "{{ .Release.Name }}"
spec: spec:
type: {{ .Values.service.type }} type: {{ .Values.service.type }}
ports: ports:

@ -264,6 +264,9 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error {
return err return err
} }
} }
if err := move(tmpPath, destPath); err != nil {
return err
}
if err := os.RemoveAll(tmpPath); err != nil { if err := os.RemoveAll(tmpPath); err != nil {
return fmt.Errorf("Failed to remove %v: %v", tmpPath, err) return fmt.Errorf("Failed to remove %v: %v", tmpPath, err)
} }
@ -610,3 +613,17 @@ func tarFromLocalDir(chartpath string, name string, repo string, version string)
return "", fmt.Errorf("can't get a valid version for dependency %s", name) return "", fmt.Errorf("can't get a valid version for dependency %s", name)
} }
// move files from tmppath to destpath
func move(tmpPath, destPath string) error {
files, _ := ioutil.ReadDir(tmpPath)
for _, file := range files {
filename := file.Name()
tmpfile := filepath.Join(tmpPath, filename)
destfile := filepath.Join(destPath, filename)
if err := os.Rename(tmpfile, destfile); err != nil {
return fmt.Errorf("Unable to move local charts to charts dir: %v", err)
}
}
return nil
}

@ -26,6 +26,9 @@ const HookAnno = "helm.sh/hook"
// HookWeightAnno is the label name for a hook weight // HookWeightAnno is the label name for a hook weight
const HookWeightAnno = "helm.sh/hook-weight" const HookWeightAnno = "helm.sh/hook-weight"
// HookDeleteAnno is the label name for the delete policy for a hook
const HookDeleteAnno = "helm.sh/hook-delete-policy"
// Types of hooks // Types of hooks
const ( const (
PreInstall = "pre-install" PreInstall = "pre-install"
@ -40,6 +43,12 @@ const (
ReleaseTestFailure = "test-failure" ReleaseTestFailure = "test-failure"
) )
// Type of policy for deleting the hook
const (
HookSucceeded = "hook-succeeded"
HookFailed = "hook-failed"
)
// FilterTestHooks filters the list of hooks are returns only testing hooks. // FilterTestHooks filters the list of hooks are returns only testing hooks.
func FilterTestHooks(hooks []*release.Hook) []*release.Hook { func FilterTestHooks(hooks []*release.Hook) []*release.Hook {
testHooks := []*release.Hook{} testHooks := []*release.Hook{}

@ -58,7 +58,7 @@ func Templates(linter *support.Linter) {
APIVersions: chartutil.DefaultVersionSet, APIVersions: chartutil.DefaultVersionSet,
KubeVersion: &version.Info{ KubeVersion: &version.Info{
Major: "1", Major: "1",
Minor: "6", Minor: "7",
GoVersion: runtime.Version(), GoVersion: runtime.Version(),
Compiler: runtime.Compiler, Compiler: runtime.Compiler,
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),

@ -6,19 +6,9 @@ Package release is a generated protocol buffer package.
It is generated from these files: It is generated from these files:
hapi/release/hook.proto hapi/release/hook.proto
hapi/release/info.proto
hapi/release/release.proto
hapi/release/status.proto
hapi/release/test_run.proto
hapi/release/test_suite.proto
It has these top-level messages: It has these top-level messages:
Hook Hook
Info
Release
Status
TestRun
TestSuite
*/ */
package release package release
@ -86,6 +76,27 @@ func (x Hook_Event) String() string {
} }
func (Hook_Event) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } func (Hook_Event) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }
type Hook_DeletePolicy int32
const (
Hook_SUCCEEDED Hook_DeletePolicy = 0
Hook_FAILED Hook_DeletePolicy = 1
)
var Hook_DeletePolicy_name = map[int32]string{
0: "SUCCEEDED",
1: "FAILED",
}
var Hook_DeletePolicy_value = map[string]int32{
"SUCCEEDED": 0,
"FAILED": 1,
}
func (x Hook_DeletePolicy) String() string {
return proto.EnumName(Hook_DeletePolicy_name, int32(x))
}
func (Hook_DeletePolicy) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 1} }
// Hook defines a hook object. // Hook defines a hook object.
type Hook struct { type Hook struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
@ -101,6 +112,8 @@ type Hook struct {
LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"`
// Weight indicates the sort order for execution among similar Hook type // Weight indicates the sort order for execution among similar Hook type
Weight int32 `protobuf:"varint,7,opt,name=weight" json:"weight,omitempty"` Weight int32 `protobuf:"varint,7,opt,name=weight" json:"weight,omitempty"`
// DeletePolicies are the policies that indicate when to delete the hook
DeletePolicies []Hook_DeletePolicy `protobuf:"varint,8,rep,packed,name=delete_policies,json=deletePolicies,enum=hapi.release.Hook_DeletePolicy" json:"delete_policies,omitempty"`
} }
func (m *Hook) Reset() { *m = Hook{} } func (m *Hook) Reset() { *m = Hook{} }
@ -157,37 +170,48 @@ func (m *Hook) GetWeight() int32 {
return 0 return 0
} }
func (m *Hook) GetDeletePolicies() []Hook_DeletePolicy {
if m != nil {
return m.DeletePolicies
}
return nil
}
func init() { func init() {
proto.RegisterType((*Hook)(nil), "hapi.release.Hook") proto.RegisterType((*Hook)(nil), "hapi.release.Hook")
proto.RegisterEnum("hapi.release.Hook_Event", Hook_Event_name, Hook_Event_value) proto.RegisterEnum("hapi.release.Hook_Event", Hook_Event_name, Hook_Event_value)
proto.RegisterEnum("hapi.release.Hook_DeletePolicy", Hook_DeletePolicy_name, Hook_DeletePolicy_value)
} }
func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 371 bytes of a gzipped FileDescriptorProto // 428 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xdf, 0x8e, 0x93, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xd1, 0x6e, 0xda, 0x30,
0x14, 0x87, 0x65, 0x5b, 0xa0, 0x3d, 0x5d, 0xd7, 0x71, 0x62, 0x74, 0xd2, 0x1b, 0xc9, 0x5e, 0x71, 0x14, 0x86, 0x9b, 0x12, 0x12, 0x38, 0x50, 0xea, 0x59, 0xd3, 0x66, 0x71, 0x53, 0xc4, 0x15, 0xbb,
0x35, 0x98, 0x35, 0x3e, 0x00, 0xdb, 0x1d, 0x75, 0xb3, 0x84, 0x36, 0x03, 0xc4, 0xc4, 0x1b, 0xc2, 0x09, 0x53, 0xa7, 0x3d, 0x40, 0x4a, 0xce, 0xd6, 0xaa, 0x11, 0x20, 0x27, 0x68, 0xd2, 0x6e, 0xa2,
0xc6, 0x69, 0x21, 0x2d, 0x0c, 0x29, 0x53, 0x7d, 0x33, 0x9f, 0xc4, 0x07, 0x32, 0x33, 0xfc, 0x89, 0x74, 0xb8, 0x10, 0x11, 0xe2, 0x88, 0x98, 0x4d, 0x7b, 0xa6, 0xbd, 0xce, 0x1e, 0x68, 0xb2, 0x09,
0x89, 0x77, 0x67, 0xbe, 0xdf, 0xc7, 0x39, 0x9c, 0x03, 0xef, 0xca, 0xa2, 0xad, 0x82, 0xb3, 0x38, 0x59, 0xa5, 0xed, 0xee, 0x9c, 0xef, 0x7c, 0x76, 0xce, 0x1f, 0xc3, 0xdb, 0x6d, 0x5a, 0x66, 0xd3,
0x89, 0xa2, 0x13, 0x41, 0x29, 0xe5, 0x91, 0xb6, 0x67, 0xa9, 0x24, 0xbe, 0xd6, 0x01, 0x1d, 0x82, 0x83, 0xc8, 0x45, 0x5a, 0x89, 0xe9, 0x56, 0xca, 0x9d, 0x57, 0x1e, 0xa4, 0x92, 0xb4, 0xaf, 0x07,
0xf5, 0xfb, 0x83, 0x94, 0x87, 0x93, 0x08, 0x4c, 0xf6, 0x7c, 0xd9, 0x07, 0xaa, 0xaa, 0x45, 0xa7, 0x5e, 0x3d, 0x18, 0xde, 0x6c, 0xa4, 0xdc, 0xe4, 0x62, 0x6a, 0x66, 0x4f, 0xc7, 0xe7, 0xa9, 0xca,
0x8a, 0xba, 0xed, 0xf5, 0xdb, 0xdf, 0x33, 0x98, 0x7f, 0x95, 0xf2, 0x88, 0x31, 0xcc, 0x9b, 0xa2, 0xf6, 0xa2, 0x52, 0xe9, 0xbe, 0x3c, 0xe9, 0xe3, 0x5f, 0x36, 0xd8, 0xf7, 0x52, 0xee, 0x28, 0x05,
0x16, 0xc4, 0xf2, 0x2c, 0x7f, 0xc9, 0x4d, 0xad, 0xd9, 0xb1, 0x6a, 0x7e, 0x90, 0xab, 0x9e, 0xe9, 0xbb, 0x48, 0xf7, 0x82, 0x59, 0x23, 0x6b, 0xd2, 0xe5, 0xa6, 0xd6, 0x6c, 0x97, 0x15, 0x6b, 0x76,
0x5a, 0xb3, 0xb6, 0x50, 0x25, 0x99, 0xf5, 0x4c, 0xd7, 0x78, 0x0d, 0x8b, 0xba, 0x68, 0xaa, 0xbd, 0x79, 0x62, 0xba, 0xd6, 0xac, 0x4c, 0xd5, 0x96, 0xb5, 0x4e, 0x4c, 0xd7, 0x74, 0x08, 0x9d, 0x7d,
0xe8, 0x14, 0x99, 0x1b, 0x3e, 0xbd, 0xf1, 0x07, 0x70, 0xc4, 0x4f, 0xd1, 0xa8, 0x8e, 0xd8, 0xde, 0x5a, 0x64, 0xcf, 0xa2, 0x52, 0xcc, 0x36, 0xbc, 0xe9, 0xe9, 0x7b, 0x70, 0xc4, 0x77, 0x51, 0xa8,
0xcc, 0xbf, 0xb9, 0x23, 0xf4, 0xdf, 0x1f, 0xa4, 0x7a, 0x36, 0x65, 0x5a, 0xe0, 0x83, 0x87, 0x3f, 0x8a, 0xb5, 0x47, 0xad, 0xc9, 0xe0, 0x96, 0x79, 0x2f, 0x17, 0xf4, 0xf4, 0xb7, 0x3d, 0xd4, 0x02,
0xc1, 0xe2, 0x54, 0x74, 0x2a, 0x3f, 0x5f, 0x1a, 0xe2, 0x78, 0x96, 0xbf, 0xba, 0x5b, 0xd3, 0x7e, 0xaf, 0x3d, 0xfa, 0x11, 0x3a, 0x79, 0x5a, 0xa9, 0xe4, 0x70, 0x2c, 0x98, 0x33, 0xb2, 0x26, 0xbd,
0x0d, 0x3a, 0xae, 0x41, 0xd3, 0x71, 0x0d, 0xee, 0x6a, 0x97, 0x5f, 0x1a, 0xfc, 0x16, 0x9c, 0x5f, 0xdb, 0xa1, 0x77, 0x8a, 0xe1, 0x9d, 0x63, 0x78, 0xf1, 0x39, 0x06, 0x77, 0xb5, 0xcb, 0x8f, 0x05,
0xa2, 0x3a, 0x94, 0x8a, 0xb8, 0x9e, 0xe5, 0xdb, 0x7c, 0x78, 0xdd, 0xfe, 0xb1, 0xc0, 0x36, 0x03, 0x7d, 0x03, 0xce, 0x0f, 0x91, 0x6d, 0xb6, 0x8a, 0xb9, 0x23, 0x6b, 0xd2, 0xe6, 0x75, 0x47, 0xef,
0xf0, 0x0a, 0xdc, 0x2c, 0x7e, 0x8a, 0xb7, 0xdf, 0x62, 0xf4, 0x02, 0xbf, 0x82, 0xd5, 0x8e, 0xb3, 0xe1, 0x7a, 0x2d, 0x72, 0xa1, 0x44, 0x52, 0xca, 0x3c, 0xfb, 0x96, 0x89, 0x8a, 0x75, 0xcc, 0x26,
0xfc, 0x31, 0x4e, 0xd2, 0x30, 0x8a, 0x90, 0x85, 0x11, 0x5c, 0xef, 0xb6, 0x49, 0x3a, 0x91, 0x2b, 0x37, 0xff, 0xd9, 0x24, 0x30, 0xe6, 0x52, 0x8b, 0x3f, 0xf9, 0x60, 0xfd, 0xb7, 0xcb, 0x44, 0x35,
0x7c, 0x03, 0xa0, 0x95, 0x07, 0x16, 0xb1, 0x94, 0xa1, 0x99, 0xf9, 0x44, 0x1b, 0x03, 0x98, 0x8f, 0xfe, 0x6d, 0x41, 0xdb, 0xac, 0x4a, 0x7b, 0xe0, 0xae, 0xe6, 0x8f, 0xf3, 0xc5, 0x97, 0x39, 0xb9,
0x3d, 0xb2, 0xdd, 0x17, 0x1e, 0x3e, 0x30, 0x64, 0x4f, 0x3d, 0x46, 0xe2, 0x18, 0xc2, 0x59, 0xce, 0xa0, 0xd7, 0xd0, 0x5b, 0x72, 0x4c, 0x1e, 0xe6, 0x51, 0xec, 0x87, 0x21, 0xb1, 0x28, 0x81, 0xfe,
0xb7, 0x51, 0x74, 0x1f, 0x6e, 0x9e, 0x90, 0x8b, 0x5f, 0xc3, 0x4b, 0xe3, 0x4c, 0x68, 0x81, 0x09, 0x72, 0x11, 0xc5, 0x0d, 0xb9, 0xa4, 0x03, 0x00, 0xad, 0x04, 0x18, 0x62, 0x8c, 0xa4, 0x65, 0x8e,
0xbc, 0xe1, 0x2c, 0x62, 0x61, 0xc2, 0xf2, 0x94, 0x25, 0x69, 0x9e, 0x64, 0x9b, 0x0d, 0x4b, 0x12, 0x68, 0xa3, 0x06, 0xf6, 0xf9, 0x8e, 0xd5, 0xf2, 0x33, 0xf7, 0x03, 0x24, 0xed, 0xe6, 0x8e, 0x33,
0xb4, 0xfc, 0x2f, 0xf9, 0x1c, 0x3e, 0x46, 0x19, 0x67, 0x08, 0xee, 0x97, 0xdf, 0xdd, 0xe1, 0x86, 0x71, 0x0c, 0xe1, 0x98, 0xf0, 0x45, 0x18, 0xde, 0xf9, 0xb3, 0x47, 0xe2, 0xd2, 0x57, 0x70, 0x65,
0xcf, 0x8e, 0x39, 0xcb, 0xc7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x3c, 0x7a, 0x0e, 0x14, 0x9c, 0x06, 0x75, 0x28, 0x83, 0xd7, 0x1c, 0x43, 0xf4, 0x23, 0x4c, 0x62, 0x8c, 0xe2, 0x24, 0x5a,
0x02, 0x00, 0x00, 0xcd, 0x66, 0x18, 0x45, 0xa4, 0xfb, 0xcf, 0xe4, 0x93, 0xff, 0x10, 0xae, 0x38, 0x12, 0x18, 0xbf,
0x83, 0xfe, 0xcb, 0xd8, 0xf4, 0x0a, 0xba, 0xe6, 0x18, 0x06, 0x18, 0x90, 0x0b, 0x0a, 0xe0, 0x68,
0x17, 0x03, 0x62, 0xdd, 0x75, 0xbf, 0xba, 0xf5, 0xef, 0x7a, 0x72, 0xcc, 0x5b, 0x7c, 0xf8, 0x13,
0x00, 0x00, 0xff, 0xff, 0xb9, 0x8a, 0xe1, 0xaf, 0x89, 0x02, 0x00, 0x00,
} }

@ -54,6 +54,7 @@ func TestLoadChartRepository(t *testing.T) {
filepath.Join(testRepository, "frobnitz-1.2.3.tgz"), filepath.Join(testRepository, "frobnitz-1.2.3.tgz"),
filepath.Join(testRepository, "sprocket-1.1.0.tgz"), filepath.Join(testRepository, "sprocket-1.1.0.tgz"),
filepath.Join(testRepository, "sprocket-1.2.0.tgz"), filepath.Join(testRepository, "sprocket-1.2.0.tgz"),
filepath.Join(testRepository, "universe/zarthal-1.0.0.tgz"),
} }
if r.Config.Name != testRepository { if r.Config.Name != testRepository {
@ -118,8 +119,8 @@ func verifyIndex(t *testing.T, actual *IndexFile) {
} }
entries := actual.Entries entries := actual.Entries
if numEntries := len(entries); numEntries != 2 { if numEntries := len(entries); numEntries != 3 {
t.Errorf("Expected 2 charts to be listed in index file but got %v", numEntries) t.Errorf("Expected 3 charts to be listed in index file but got %v", numEntries)
} }
expects := map[string]ChartVersions{ expects := map[string]ChartVersions{
@ -145,6 +146,14 @@ func verifyIndex(t *testing.T, actual *IndexFile) {
}, },
}, },
}, },
"zarthal": {
{
Metadata: &chart.Metadata{
Name: "zarthal",
Version: "1.0.0",
},
},
},
} }
for name, versions := range expects { for name, versions := range expects {

@ -231,9 +231,26 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz"))
if err != nil {
return nil, err
}
archives = append(archives, moreArchives...)
index := NewIndexFile() index := NewIndexFile()
for _, arch := range archives { for _, arch := range archives {
fname := filepath.Base(arch) fname, err := filepath.Rel(dir, arch)
if err != nil {
return index, err
}
var parentDir string
parentDir, fname = filepath.Split(fname)
parentURL, err := urlutil.URLJoin(baseURL, parentDir)
if err != nil {
parentURL = filepath.Join(baseURL, parentDir)
}
c, err := chartutil.Load(arch) c, err := chartutil.Load(arch)
if err != nil { if err != nil {
// Assume this is not a chart. // Assume this is not a chart.
@ -243,7 +260,7 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
if err != nil { if err != nil {
return index, err return index, err
} }
index.Add(c.Metadata, fname, baseURL, hash) index.Add(c.Metadata, fname, parentURL, hash)
} }
return index, nil return index, nil
} }

@ -278,13 +278,20 @@ func TestIndexDirectory(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if l := len(index.Entries); l != 2 { if l := len(index.Entries); l != 3 {
t.Fatalf("Expected 2 entries, got %d", l) t.Fatalf("Expected 3 entries, got %d", l)
} }
// Other things test the entry generation more thoroughly. We just test a // Other things test the entry generation more thoroughly. We just test a
// few fields. // few fields.
cname := "frobnitz"
corpus := []struct{ chartName, downloadLink string }{
{"frobnitz", "http://localhost:8080/frobnitz-1.2.3.tgz"},
{"zarthal", "http://localhost:8080/universe/zarthal-1.0.0.tgz"},
}
for _, test := range corpus {
cname := test.chartName
frobs, ok := index.Entries[cname] frobs, ok := index.Entries[cname]
if !ok { if !ok {
t.Fatalf("Could not read chart %s", cname) t.Fatalf("Could not read chart %s", cname)
@ -294,11 +301,12 @@ func TestIndexDirectory(t *testing.T) {
if len(frob.Digest) == 0 { if len(frob.Digest) == 0 {
t.Errorf("Missing digest of file %s.", frob.Name) t.Errorf("Missing digest of file %s.", frob.Name)
} }
if frob.URLs[0] != "http://localhost:8080/frobnitz-1.2.3.tgz" { if frob.URLs[0] != test.downloadLink {
t.Errorf("Unexpected URLs: %v", frob.URLs) t.Errorf("Unexpected URLs: %v", frob.URLs)
} }
if frob.Name != "frobnitz" { if frob.Name != cname {
t.Errorf("Expected frobnitz, got %q", frob.Name) t.Errorf("Expected %q, got %q", cname, frob.Name)
}
} }
} }

@ -44,16 +44,22 @@ var events = map[string]release.Hook_Event{
hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE, hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE,
} }
// manifest represents a manifest file, which has a name and some content. // deletePolices represents a mapping between the key in the annotation for label deleting policy and its real meaning
type manifest struct { var deletePolices = map[string]release.Hook_DeletePolicy{
name string hooks.HookSucceeded: release.Hook_SUCCEEDED,
content string hooks.HookFailed: release.Hook_FAILED,
head *util.SimpleHead }
// Manifest represents a manifest file, which has a name and some content.
type Manifest struct {
Name string
Content string
Head *util.SimpleHead
} }
type result struct { type result struct {
hooks []*release.Hook hooks []*release.Hook
generic []manifest generic []Manifest
} }
type manifestFile struct { type manifestFile struct {
@ -71,7 +77,7 @@ type manifestFile struct {
// //
// Files that do not parse into the expected format are simply placed into a map and // Files that do not parse into the expected format are simply placed into a map and
// returned. // returned.
func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []manifest, error) { func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) {
result := &result{} result := &result{}
for filePath, c := range files { for filePath, c := range files {
@ -113,6 +119,13 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
// annotations: // annotations:
// helm.sh/hook: pre-install // helm.sh/hook: pre-install
// //
// To determine the policy to delete the hook, it looks for a YAML structure like this:
//
// kind: SomeKind
// apiVersion: v1
// metadata:
// annotations:
// helm.sh/hook-delete-policy: hook-succeeded
func (file *manifestFile) sort(result *result) error { func (file *manifestFile) sort(result *result) error {
for _, m := range file.entries { for _, m := range file.entries {
var entry util.SimpleHead var entry util.SimpleHead
@ -128,20 +141,20 @@ func (file *manifestFile) sort(result *result) error {
} }
if !hasAnyAnnotation(entry) { if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, manifest{ result.generic = append(result.generic, Manifest{
name: file.path, Name: file.path,
content: m, Content: m,
head: &entry, Head: &entry,
}) })
continue continue
} }
hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno] hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
if !ok { if !ok {
result.generic = append(result.generic, manifest{ result.generic = append(result.generic, Manifest{
name: file.path, Name: file.path,
content: m, Content: m,
head: &entry, Head: &entry,
}) })
continue continue
} }
@ -155,24 +168,42 @@ func (file *manifestFile) sort(result *result) error {
Manifest: m, Manifest: m,
Events: []release.Hook_Event{}, Events: []release.Hook_Event{},
Weight: hw, Weight: hw,
DeletePolicies: []release.Hook_DeletePolicy{},
} }
isKnownHook := false isUnknownHook := false
for _, hookType := range strings.Split(hookTypes, ",") { for _, hookType := range strings.Split(hookTypes, ",") {
hookType = strings.ToLower(strings.TrimSpace(hookType)) hookType = strings.ToLower(strings.TrimSpace(hookType))
e, ok := events[hookType] e, ok := events[hookType]
if ok { if !ok {
isKnownHook = true isUnknownHook = true
h.Events = append(h.Events, e) break
} }
h.Events = append(h.Events, e)
} }
if !isKnownHook { if isUnknownHook {
log.Printf("info: skipping unknown hook: %q", hookTypes) log.Printf("info: skipping unknown hook: %q", hookTypes)
continue continue
} }
result.hooks = append(result.hooks, h) result.hooks = append(result.hooks, h)
isKnownDeletePolices := false
dps, ok := entry.Metadata.Annotations[hooks.HookDeleteAnno]
if ok {
for _, dp := range strings.Split(dps, ",") {
dp = strings.ToLower(strings.TrimSpace(dp))
p, exist := deletePolices[dp]
if exist {
isKnownDeletePolices = true
h.DeletePolicies = append(h.DeletePolicies, p)
}
}
if !isKnownDeletePolices {
log.Printf("info: skipping unknown hook delete policy: %q", dps)
}
}
} }
return nil return nil

@ -194,7 +194,7 @@ metadata:
} }
// Verify the sort order // Verify the sort order
sorted := []manifest{} sorted := []Manifest{}
for _, s := range data { for _, s := range data {
manifests := util.SplitManifests(s.manifest) manifests := util.SplitManifests(s.manifest)
@ -211,10 +211,10 @@ metadata:
//only keep track of non-hook manifests //only keep track of non-hook manifests
if err == nil && s.hooks[name] == nil { if err == nil && s.hooks[name] == nil {
another := manifest{ another := Manifest{
content: m, Content: m,
name: name, Name: name,
head: &sh, Head: &sh,
} }
sorted = append(sorted, another) sorted = append(sorted, another)
} }
@ -223,8 +223,8 @@ metadata:
sorted = sortByKind(sorted, InstallOrder) sorted = sortByKind(sorted, InstallOrder)
for i, m := range generic { for i, m := range generic {
if m.content != sorted[i].content { if m.Content != sorted[i].Content {
t.Errorf("Expected %q, got %q", m.content, sorted[i].content) t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content)
} }
} }
} }

@ -84,7 +84,7 @@ var UninstallOrder SortOrder = []string{
// sortByKind does an in-place sort of manifests by Kind. // sortByKind does an in-place sort of manifests by Kind.
// //
// Results are sorted by 'ordering' // Results are sorted by 'ordering'
func sortByKind(manifests []manifest, ordering SortOrder) []manifest { func sortByKind(manifests []Manifest, ordering SortOrder) []Manifest {
ks := newKindSorter(manifests, ordering) ks := newKindSorter(manifests, ordering)
sort.Sort(ks) sort.Sort(ks)
return ks.manifests return ks.manifests
@ -92,10 +92,10 @@ func sortByKind(manifests []manifest, ordering SortOrder) []manifest {
type kindSorter struct { type kindSorter struct {
ordering map[string]int ordering map[string]int
manifests []manifest manifests []Manifest
} }
func newKindSorter(m []manifest, s SortOrder) *kindSorter { func newKindSorter(m []Manifest, s SortOrder) *kindSorter {
o := make(map[string]int, len(s)) o := make(map[string]int, len(s))
for v, k := range s { for v, k := range s {
o[k] = v o[k] = v
@ -114,11 +114,11 @@ func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifes
func (k *kindSorter) Less(i, j int) bool { func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i] a := k.manifests[i]
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 first == second { if first == second {
// same kind (including unknown) so sub sort alphanumeric // same kind (including unknown) so sub sort alphanumeric
return a.name < b.name return a.Name < b.Name
} }
// unknown kind is last // unknown kind is last
if !aok { if !aok {
@ -130,3 +130,11 @@ func (k *kindSorter) Less(i, j int) bool {
// sort different kinds // sort different kinds
return first < second return first < second
} }
// SortByKind sorts manifests in InstallOrder
func SortByKind(manifests []Manifest) []Manifest {
ordering := InstallOrder
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
}

@ -24,103 +24,102 @@ import (
) )
func TestKindSorter(t *testing.T) { func TestKindSorter(t *testing.T) {
manifests := []manifest{ manifests := []Manifest{
{ {
name: "i", Name: "i",
head: &util.SimpleHead{Kind: "ClusterRole"}, Head: &util.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
name: "j", Name: "j",
head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
name: "e", Name: "e",
head: &util.SimpleHead{Kind: "ConfigMap"}, Head: &util.SimpleHead{Kind: "ConfigMap"},
}, },
{ {
name: "u", Name: "u",
head: &util.SimpleHead{Kind: "CronJob"}, Head: &util.SimpleHead{Kind: "CronJob"},
}, },
{ {
name: "n", Name: "n",
head: &util.SimpleHead{Kind: "DaemonSet"}, Head: &util.SimpleHead{Kind: "DaemonSet"},
}, },
{ {
name: "r", Name: "r",
head: &util.SimpleHead{Kind: "Deployment"}, Head: &util.SimpleHead{Kind: "Deployment"},
}, },
{ {
name: "!", Name: "!",
head: &util.SimpleHead{Kind: "HonkyTonkSet"}, Head: &util.SimpleHead{Kind: "HonkyTonkSet"},
}, },
{ {
name: "v", Name: "v",
head: &util.SimpleHead{Kind: "Ingress"}, Head: &util.SimpleHead{Kind: "Ingress"},
}, },
{ {
name: "t", Name: "t",
head: &util.SimpleHead{Kind: "Job"}, Head: &util.SimpleHead{Kind: "Job"},
}, },
{ {
name: "c", Name: "c",
head: &util.SimpleHead{Kind: "LimitRange"}, Head: &util.SimpleHead{Kind: "LimitRange"},
}, },
{ {
name: "a", Name: "a",
head: &util.SimpleHead{Kind: "Namespace"}, Head: &util.SimpleHead{Kind: "Namespace"},
}, },
{ {
name: "f", Name: "f",
head: &util.SimpleHead{Kind: "PersistentVolume"}, Head: &util.SimpleHead{Kind: "PersistentVolume"},
}, },
{ {
name: "g", Name: "g",
head: &util.SimpleHead{Kind: "PersistentVolumeClaim"}, Head: &util.SimpleHead{Kind: "PersistentVolumeClaim"},
}, },
{ {
name: "o", Name: "o",
head: &util.SimpleHead{Kind: "Pod"}, Head: &util.SimpleHead{Kind: "Pod"},
}, },
{ {
name: "q", Name: "q",
head: &util.SimpleHead{Kind: "ReplicaSet"}, Head: &util.SimpleHead{Kind: "ReplicaSet"},
}, },
{ {
name: "p", Name: "p",
head: &util.SimpleHead{Kind: "ReplicationController"}, Head: &util.SimpleHead{Kind: "ReplicationController"},
}, },
{ {
name: "b", Name: "b",
head: &util.SimpleHead{Kind: "ResourceQuota"}, Head: &util.SimpleHead{Kind: "ResourceQuota"},
}, },
{ {
name: "k", Name: "k",
head: &util.SimpleHead{Kind: "Role"}, Head: &util.SimpleHead{Kind: "Role"},
}, },
{ {
name: "l", Name: "l",
head: &util.SimpleHead{Kind: "RoleBinding"}, Head: &util.SimpleHead{Kind: "RoleBinding"},
}, },
{ {
name: "d", Name: "d",
head: &util.SimpleHead{Kind: "Secret"}, Head: &util.SimpleHead{Kind: "Secret"},
}, },
{ {
name: "m", Name: "m",
head: &util.SimpleHead{Kind: "Service"}, Head: &util.SimpleHead{Kind: "Service"},
}, },
{ {
name: "h", Name: "h",
head: &util.SimpleHead{Kind: "ServiceAccount"}, Head: &util.SimpleHead{Kind: "ServiceAccount"},
}, },
{ {
name: "s", Name: "s",
head: &util.SimpleHead{Kind: "StatefulSet"}, Head: &util.SimpleHead{Kind: "StatefulSet"},
}, },
{ {
name: "w", Name: "w",
content: "", Head: &util.SimpleHead{Kind: "APIService"},
head: &util.SimpleHead{Kind: "APIService"},
}, },
} }
@ -139,7 +138,7 @@ func TestKindSorter(t *testing.T) {
} }
defer buf.Reset() defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) { for _, r := range sortByKind(manifests, test.order) {
buf.WriteString(r.name) buf.WriteString(r.Name)
} }
if got := buf.String(); got != test.expected { if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got) t.Errorf("Expected %q, got %q", test.expected, got)
@ -150,42 +149,42 @@ func TestKindSorter(t *testing.T) {
// TestKindSorterSubSort verifies manifests of same kind are also sorted alphanumeric // TestKindSorterSubSort verifies manifests of same kind are also sorted alphanumeric
func TestKindSorterSubSort(t *testing.T) { func TestKindSorterSubSort(t *testing.T) {
manifests := []manifest{ manifests := []Manifest{
{ {
name: "a", Name: "a",
head: &util.SimpleHead{Kind: "ClusterRole"}, Head: &util.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
name: "A", Name: "A",
head: &util.SimpleHead{Kind: "ClusterRole"}, Head: &util.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
name: "0", Name: "0",
head: &util.SimpleHead{Kind: "ConfigMap"}, Head: &util.SimpleHead{Kind: "ConfigMap"},
}, },
{ {
name: "1", Name: "1",
head: &util.SimpleHead{Kind: "ConfigMap"}, Head: &util.SimpleHead{Kind: "ConfigMap"},
}, },
{ {
name: "z", Name: "z",
head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
name: "!", Name: "!",
head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
name: "u3", Name: "u3",
head: &util.SimpleHead{Kind: "Unknown"}, Head: &util.SimpleHead{Kind: "Unknown"},
}, },
{ {
name: "u1", Name: "u1",
head: &util.SimpleHead{Kind: "Unknown"}, Head: &util.SimpleHead{Kind: "Unknown"},
}, },
{ {
name: "u2", Name: "u2",
head: &util.SimpleHead{Kind: "Unknown"}, Head: &util.SimpleHead{Kind: "Unknown"},
}, },
} }
for _, test := range []struct { for _, test := range []struct {
@ -200,7 +199,7 @@ func TestKindSorterSubSort(t *testing.T) {
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {
defer buf.Reset() defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) { for _, r := range sortByKind(manifests, test.order) {
buf.WriteString(r.name) buf.WriteString(r.Name)
} }
if got := buf.String(); got != test.expected { if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got) t.Errorf("Expected %q, got %q", test.expected, got)

@ -166,7 +166,7 @@ func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient env
errs = []error{} errs = []error{}
for _, file := range filesToDelete { for _, file := range filesToDelete {
b := bytes.NewBufferString(strings.TrimSpace(file.content)) b := bytes.NewBufferString(strings.TrimSpace(file.Content))
if b.Len() == 0 { if b.Len() == 0 {
continue continue
} }

@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
@ -299,8 +300,8 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// Aggregate all valid manifests into one big doc. // Aggregate all valid manifests into one big doc.
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
for _, m := range manifests { for _, m := range manifests {
b.WriteString("\n---\n# Source: " + m.name + "\n") b.WriteString("\n---\n# Source: " + m.Name + "\n")
b.WriteString(m.content) b.WriteString(m.Content)
} }
return hooks, b, notes, nil return hooks, b, notes, nil
@ -347,12 +348,36 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
b.WriteString(h.Manifest) b.WriteString(h.Manifest)
if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil { if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil {
s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err) s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err)
// If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted
// under failed condition. If so, then clear the corresponding resource object in the hook
if hookShouldBeDeleted(h, hooks.HookFailed) {
b.Reset()
b.WriteString(h.Manifest)
s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, hooks.HookFailed)
if errHookDelete := kubeCli.Delete(namespace, b); errHookDelete != nil {
s.Log("warning: Release %s %s %S could not be deleted: %s", name, hook, h.Path, errHookDelete)
return errHookDelete
}
}
return err return err
} }
h.LastRun = timeconv.Now()
} }
s.Log("hooks complete for %s %s", hook, name) s.Log("hooks complete for %s %s", hook, name)
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks {
b := bytes.NewBufferString(h.Manifest)
if hookShouldBeDeleted(h, hooks.HookSucceeded) {
s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, hooks.HookSucceeded)
if errHookDelete := kubeCli.Delete(namespace, b); errHookDelete != nil {
s.Log("warning: Release %s %s %S could not be deleted: %s", name, hook, h.Path, errHookDelete)
return errHookDelete
}
}
h.LastRun = timeconv.Now()
}
return nil return nil
} }
@ -373,3 +398,16 @@ func validateReleaseName(releaseName string) error {
return nil return nil
} }
// hookShouldBeDeleted 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.
func hookShouldBeDeleted(hook *release.Hook, policy string) bool {
if dp, ok := deletePolices[policy]; ok {
for _, v := range hook.DeletePolicies {
if dp == v {
return true
}
}
}
return false
}

@ -29,18 +29,18 @@ const resourcePolicyAnno = "helm.sh/resource-policy"
// during an uninstallRelease action. // during an uninstallRelease action.
const keepPolicy = "keep" const keepPolicy = "keep"
func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) { func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) {
remaining := []manifest{} remaining := []Manifest{}
keep := []manifest{} keep := []Manifest{}
for _, m := range manifests { for _, m := range manifests {
if m.head.Metadata == nil || m.head.Metadata.Annotations == nil || len(m.head.Metadata.Annotations) == 0 { if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 {
remaining = append(remaining, m) remaining = append(remaining, m)
continue continue
} }
resourcePolicyType, ok := m.head.Metadata.Annotations[resourcePolicyAnno] resourcePolicyType, ok := m.Head.Metadata.Annotations[resourcePolicyAnno]
if !ok { if !ok {
remaining = append(remaining, m) remaining = append(remaining, m)
continue continue
@ -55,10 +55,10 @@ func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) {
return keep, remaining return keep, remaining
} }
func summarizeKeptManifests(manifests []manifest) string { func summarizeKeptManifests(manifests []Manifest) string {
message := "These resources were kept due to the resource policy:\n" message := "These resources were kept due to the resource policy:\n"
for _, m := range manifests { for _, m := range manifests {
details := "[" + m.head.Kind + "] " + m.head.Metadata.Name + "\n" details := "[" + m.Head.Kind + "] " + m.Head.Metadata.Name + "\n"
message = message + details message = message + details
} }
return message return message

@ -130,7 +130,7 @@ downloadFile() {
# installs it. # installs it.
installFile() { installFile() {
HELM_TMP="/tmp/$PROJECT_NAME" HELM_TMP="/tmp/$PROJECT_NAME"
local sum=$(openssl sha -sha256 ${HELM_TMP_FILE} | awk '{print $2}') local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}')
local expected_sum=$(cat ${HELM_SUM_FILE}) local expected_sum=$(cat ${HELM_SUM_FILE})
if [ "$sum" != "$expected_sum" ]; then if [ "$sum" != "$expected_sum" ]; then
echo "SHA sum of $HELM_TMP does not match. Aborting." echo "SHA sum of $HELM_TMP does not match. Aborting."
@ -192,8 +192,13 @@ set -u
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
'--version'|-v) '--version'|-v)
export DESIRED_VERSION="${2}"
shift shift
if [[ $# -ne 0 ]]; then
export DESIRED_VERSION="${1}"
else
echo -e "Please provide the desired version. e.g. --version v2.4.0 or -v latest"
exit 0
fi
;; ;;
'--help'|-h) '--help'|-h)
help help

Loading…
Cancel
Save