Merge branch 'master' into 2751

pull/4088/head
Jon Leonard 7 years ago committed by GitHub
commit a499cf3517
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright 2016 The Kubernetes Authors All rights reserved. # Copyright The Helm Authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.

@ -4,7 +4,7 @@ jobs:
working_directory: /go/src/k8s.io/helm working_directory: /go/src/k8s.io/helm
parallelism: 3 parallelism: 3
docker: docker:
- image: golang:1.10 - image: golang:1.11
environment: environment:
PROJECT_NAME: "kubernetes-helm" PROJECT_NAME: "kubernetes-helm"
steps: steps:

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright 2016 The Kubernetes Authors All rights reserved. # Copyright The Helm Authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright 2016 The Kubernetes Authors All rights reserved. # Copyright The Helm Authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.

1
.gitignore vendored

@ -5,6 +5,7 @@
_dist/ _dist/
_proto/*.pb.go _proto/*.pb.go
bin/ bin/
rootfs/helm
rootfs/tiller rootfs/tiller
rootfs/rudder rootfs/rudder
vendor/ vendor/

@ -5,33 +5,89 @@ The Kubernetes Helm project accepts contributions via GitHub pull requests. This
## Reporting a Security Issue ## Reporting a Security Issue
Most of the time, when you find a bug in Helm, it should be reported Most of the time, when you find a bug in Helm, it should be reported
using [GitHub issues](https://github.com/kubernetes/helm/issues). However, if using [GitHub issues](https://github.com/helm/helm/issues). However, if
you are reporting a _security vulnerability_, please email a report to you are reporting a _security vulnerability_, please email a report to
[helm-security@deis.com](mailto:helm-security@deis.com). This will give [cncf-kubernetes-helm-security@lists.cncf.io](mailto:cncf-kubernetes-helm-security@lists.cncf.io). This will give
us a chance to try to fix the issue before it is exploited in the wild. us a chance to try to fix the issue before it is exploited in the wild.
## Contributor License Agreements ## Sign Your Work
We'd love to accept your patches! Before we can take them, we have to jump a The sign-off is a simple line at the end of the explanation for a commit. All
couple of legal hurdles. commits needs to be signed. Your signature certifies that you wrote the patch or
otherwise have the right to contribute the material. The rules are pretty simple,
if you can certify the below (from [developercertificate.org](http://developercertificate.org/)):
The Cloud Native Computing Foundation (CNCF) CLA [must be signed](https://github.com/kubernetes/community/blob/master/CLA.md) by all contributors. ```
Please fill out either the individual or corporate Contributor License Developer Certificate of Origin
Agreement (CLA). Version 1.1
Once you are CLA'ed, we'll be able to accept your pull requests. For any issues that you face during this process, Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
please add a comment [here](https://github.com/kubernetes/kubernetes/issues/27796) explaining the issue and we will help get it sorted out. 1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
***NOTE***: Only original source code from you and other people that have Everyone is permitted to copy and distribute verbatim copies of this
signed the CLA can be accepted into the repository. This policy does not license document, but changing it is not allowed.
apply to [third_party](third_party/) and [vendor](vendor/).
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@example.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
Note: If your git config information is set properly then viewing the
`git log` information for your commit will look something like this:
```
Author: Joe Smith <joe.smith@example.com>
Date: Thu Feb 2 11:41:15 2018 -0800
Update README
Signed-off-by: Joe Smith <joe.smith@example.com>
```
Notice the `Author` and `Signed-off-by` lines match. If they don't
your PR will be rejected by the automated DCO check.
## Support Channels ## Support Channels
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/kubernetes/helm/issues/new) - GitHub [issues](https://github.com/helm/helm/issues/new)
- Slack: #Helm room in the [Kubernetes Slack](http://slack.kubernetes.io/) - Slack [Kubernetes Slack](http://slack.kubernetes.io/):
- User: #helm-users
- Contributor: #helm-dev
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.
@ -40,20 +96,20 @@ Before opening a new issue or submitting a new pull request, it's helpful to sea
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`
`Upcoming - Minor` is used for keeping track of issues that aren't assigned to a specific `Upcoming - Minor` is used for keeping track of issues that aren't assigned to a specific
release but could easily be addressed in a minor release. `Upcoming - Major` keeps track release but could easily be addressed in a minor release. `Upcoming - Major` keeps track
of issues that will need to be addressed in a major release. For example, if the current of issues that will need to be addressed in a major release. For example, if the current
version is `2.2.0` an issue/PR could fall in to one of 4 different active milestones: version is `2.2.0` an issue/PR could fall in to one of 4 different active milestones:
`2.2.1`, `2.3.0`, `Upcoming - Minor`, or `Upcoming - Major`. If an issue pertains to a `2.2.1`, `2.3.0`, `Upcoming - Minor`, or `Upcoming - Major`. If an issue pertains to a
specific upcoming bug or minor release, it would go into `2.2.1` or `2.3.0`. If the issue/PR specific upcoming bug or minor release, it would go into `2.2.1` or `2.3.0`. If the issue/PR
does not have a specific milestone yet, but it is likely that it will land in a `2.X` release, does not have a specific milestone yet, but it is likely that it will land in a `2.X` release,
it should go into `Upcoming - Minor`. If the issue/PR is a large functionality add or change it should go into `Upcoming - Minor`. If the issue/PR is a large functionality add or change
and/or it breaks compatibility, then it should be added to the `Upcoming - Major` milestone. and/or it breaks compatibility, then it should be added to the `Upcoming - Major` milestone.
An issue that we are not sure we will be doing will not be added to any milestone. An issue that we are not sure we will be doing will not be added to any milestone.
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 ## Semantic Versioning
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). 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).
@ -75,53 +131,54 @@ Issues are used as the primary method for tracking anything to do with the Helm
### 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
Slack channel or have particular interest to the community as a whole. Depending on the discussion, Slack channel or have particular interest to the community as a whole. Depending on the discussion,
these can turn into "Feature" or "Bug" issues. these can turn into "Feature" or "Bug" issues.
- Proposal: Used for items (like this one) that propose a new ideas or functionality that require - Proposal: Used for items (like this one) that propose a new ideas or functionality that require
a larger community discussion. This allows for feedback from others in the community before a a larger community discussion. This allows for feedback from others in the community before a
feature is actually developed. This is not needed for small additions. Final word on whether or feature is actually developed. This is not needed for small additions. Final word on whether or
not a feature needs a proposal is up to the core maintainers. All issues that are proposals should not a feature needs a proposal is up to the core maintainers. All issues that are proposals should
both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become
a "Feature" and does not require a milestone. a "Feature" and does not require a milestone.
- Features: These track specific feature requests and ideas until they are complete. They can evolve - Features: These track specific feature requests and ideas until they are complete. They can evolve
from a "Proposal" or can be submitted individually depending on the size. 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
2. Triage 2. Triage
- The maintainer in charge of triaging will apply the proper labels for the issue. This - The maintainer in charge of triaging will apply the proper labels for the issue. This
includes labels for priority, type, and metadata (such as "starter"). The only issue includes labels for priority, type, and metadata (such as "starter"). The only issue
priority we will be tracking is whether or not the issue is "critical." If additional priority we will be tracking is whether or not the issue is "critical." If additional
levels are needed in the future, we will add them. levels are needed in the future, we will add them.
- (If needed) Clean up the title to succinctly and clearly state the issue. Also ensure - (If needed) Clean up the title to succinctly and clearly state the issue. Also ensure
that proposals are prefaced with "Proposal". that proposals are prefaced with "Proposal".
- Add the issue to the correct milestone. If any questions come up, don't worry about - Add the issue to the correct milestone. If any questions come up, don't worry about
adding the issue to a milestone until the questions are answered. adding the issue to a milestone until the questions are answered.
- We attempt to do this process at least once per work day. - We attempt to do this process at least once per work day.
3. Discussion 3. Discussion
- "Feature" and "Bug" issues should be connected to the PR that resolves it. - "Feature" and "Bug" issues should be connected to the PR that resolves it.
- Whoever is working on a "Feature" or "Bug" issue (whether a maintainer or someone from - Whoever is working on a "Feature" or "Bug" issue (whether a maintainer or someone from
the community), should either assign the issue to them self or make a comment in the issue the community), should either assign the issue to them self or make a comment in the issue
saying that they are taking it. saying that they are taking it.
- "Proposal" and "Question" issues should stay open until resolved or if they have not been - "Proposal" and "Question" issues should stay open until resolved or if they have not been
active for more than 30 days. This will help keep the issue queue to a manageable size and active for more than 30 days. This will help keep the issue queue to a manageable size and
reduce noise. Should the issue need to stay open, the `keep open` label can be added. reduce noise. Should the issue need to stay open, the `keep open` label can be added.
4. Issue closure 4. Issue closure
## How to Contribute a Patch ## How to Contribute a Patch
1. If you haven't already done so, sign a Contributor License Agreement (see details above). 1. Fork the repo, develop and test your code changes.
2. Fork the desired repo, develop and test your code changes. 1. Use sign-off when making each of your commits (see [above](#sign-your-work)).
3. Submit a pull request. If you forgot to sign some commits that are part of the contribution, you can ask [git to rewrite your commit history](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History).
1. Submit a pull request.
Coding conventions and standards are explained in the official developer docs: Coding conventions and standards are explained in the official developer docs:
https://github.com/kubernetes/helm/blob/master/docs/developers.md https://github.com/helm/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
@ -133,36 +190,36 @@ Like any good open source project, we use Pull Requests to track code changes
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,
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.
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 - 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 requested a feature, are highly encouraged, but not required. Maintainer reviews **are** required
before any merge 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` 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
especially helpful when the review may take awhile. especially helpful when the review may take awhile.
- 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 merged.
- Reviewers should update labels as needed (such as `needs rebase`) - Reviewers should update labels as needed (such as `needs rebase`)
5. Address comments by answering questions or changing code 5. Address comments by answering questions or changing code
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 request another OWNER do that for them. or explicitly request another OWNER do that for them.
@ -171,14 +228,14 @@ Like any good open source project, we use Pull Requests to track code changes
#### 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
@ -213,8 +270,6 @@ The following tables define all label types used for Helm. It is split up by cat
| ----- | ----------- | | ----- | ----------- |
| `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 |
| `breaking` | The PR has breaking changes (such as API changes) | | `breaking` | The PR has breaking changes (such as API changes) |
| `cncf-cla: no` | The PR submitter has **not** signed the project CLA. |
| `cncf-cla: yes` | The PR submitter has signed the project CLA. This is required to merge. |
| `in progress` | Indicates that a maintainer is looking at the PR, even if no review has been posted yet | | `in progress` | Indicates that a maintainer is looking at the PR, even if no review has been posted yet |
| `needs pick` | Indicates that the PR needs to be picked into a feature branch (generally bugfix branches). Once it has been, the `picked` label should be applied and this one removed | | `needs pick` | Indicates that the PR needs to be picked into a feature branch (generally bugfix branches). Once it has been, the `picked` label should be applied and this one removed |
| `needs rebase` | A helper label used to indicate that the PR needs to be rebased before it can be merged. Used for easy filtering | | `needs rebase` | A helper label used to indicate that the PR needs to be rebased before it can be merged. Used for easy filtering |
@ -222,11 +277,11 @@ The following tables define all label types used for Helm. It is split up by cat
#### 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`
because it requires sign off from multiple people. Conversely, a PR that adds a small feature, but requires because it requires sign off from multiple people. Conversely, a PR that adds a small feature, but requires
another 150 lines of tests to cover all cases, could be labeled as `size/small` even though the number another 150 lines of tests to cover all cases, could be labeled as `size/small` even though the number
lines is greater than defined below. lines is greater than defined below.
| Label | Description | | Label | Description |

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2016 The Kubernetes Authors All Rights Reserved Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,10 +1,10 @@
DOCKER_REGISTRY ?= gcr.io DOCKER_REGISTRY ?= gcr.io
IMAGE_PREFIX ?= kubernetes-helm IMAGE_PREFIX ?= kubernetes-helm
DEV_IMAGE ?= golang:1.11
SHORT_NAME ?= tiller SHORT_NAME ?= tiller
SHORT_NAME_RUDDER ?= rudder SHORT_NAME_RUDDER ?= rudder
TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le windows/amd64 TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64
DIST_DIRS = find * -type d -exec DIST_DIRS = find * -type d -exec
APP = helm
# go option # go option
GO ?= go GO ?= go
@ -18,7 +18,7 @@ BINDIR := $(CURDIR)/bin
BINARIES := helm tiller BINARIES := helm tiller
# Required for globs to work correctly # Required for globs to work correctly
SHELL=/bin/bash SHELL=/usr/bin/env bash
.PHONY: all .PHONY: all
all: build all: build
@ -27,11 +27,12 @@ all: build
build: build:
GOBIN=$(BINDIR) $(GO) install $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/... GOBIN=$(BINDIR) $(GO) install $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/...
# usage: make clean build-cross dist APP=helm|tiller VERSION=v2.0.0-alpha.3 # usage: make clean build-cross dist VERSION=v2.0.0-alpha.3
.PHONY: build-cross .PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static" build-cross: LDFLAGS += -extldflags "-static"
build-cross: build-cross:
CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) $(if $(TAGS),-tags '$(TAGS)',) -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP) CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) $(if $(TAGS),-tags '$(TAGS)',) -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/helm
CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) $(if $(TAGS),-tags '$(TAGS)',) -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/tiller
.PHONY: dist .PHONY: dist
dist: dist:
@ -60,6 +61,7 @@ check-docker:
docker-binary: BINDIR = ./rootfs docker-binary: BINDIR = ./rootfs
docker-binary: GOFLAGS += -a -installsuffix cgo docker-binary: GOFLAGS += -a -installsuffix cgo
docker-binary: docker-binary:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build -o $(BINDIR)/helm $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/helm
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build -o $(BINDIR)/tiller $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/tiller GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build -o $(BINDIR)/tiller $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/tiller
.PHONY: docker-build .PHONY: docker-build
@ -86,17 +88,39 @@ test: TESTFLAGS += -race -v
test: test-style test: test-style
test: test-unit test: test-unit
.PHONY: docker-test
docker-test: docker-binary
docker-test: TESTFLAGS += -race -v
docker-test: docker-test-style
docker-test: docker-test-unit
.PHONY: test-unit .PHONY: test-unit
test-unit: test-unit:
@echo @echo
@echo "==> Running unit tests <==" @echo "==> Running unit tests <=="
HELM_HOME=/no/such/dir $(GO) test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS) HELM_HOME=/no/such/dir $(GO) test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
.PHONY: docker-test-unit
docker-test-unit: check-docker
docker run \
-v $(shell pwd):/go/src/k8s.io/helm \
-w /go/src/k8s.io/helm \
$(DEV_IMAGE) \
bash -c "HELM_HOME=/no/such/dir go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)"
.PHONY: test-style .PHONY: test-style
test-style: test-style:
@scripts/validate-go.sh @scripts/validate-go.sh
@scripts/validate-license.sh @scripts/validate-license.sh
.PHONY: docker-test-style
docker-test-style: check-docker
docker run \
-v $(CURDIR):/go/src/k8s.io/helm \
-w /go/src/k8s.io/helm \
$(DEV_IMAGE) \
bash -c "scripts/validate-go.sh && scripts/validate-license.sh"
.PHONY: protoc .PHONY: protoc
protoc: protoc:
$(MAKE) -C _proto/ all $(MAKE) -C _proto/ all

@ -28,3 +28,4 @@ emeritus:
- migmartri - migmartri
- seh - seh
- vaikas-google - vaikas-google
- rimusz

@ -1,16 +1,16 @@
# Kubernetes Helm # Helm
[![CircleCI](https://circleci.com/gh/kubernetes/helm.svg?style=svg)](https://circleci.com/gh/kubernetes/helm) [![CircleCI](https://circleci.com/gh/helm/helm.svg?style=shield)](https://circleci.com/gh/helm/helm)
[![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes/helm)](https://goreportcard.com/report/github.com/kubernetes/helm) [![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm)
[![GoDoc](https://godoc.org/github.com/kubernetes/helm?status.svg)](https://godoc.org/github.com/kubernetes/helm) [![GoDoc](https://godoc.org/k8s.io/helm?status.svg)](https://godoc.org/k8s.io/helm)
Helm is a tool for managing Kubernetes charts. Charts are packages of Helm is a tool for managing Kubernetes charts. Charts are packages of
pre-configured Kubernetes resources. pre-configured Kubernetes resources.
Use Helm to: Use Helm to:
- Find and use [popular software packaged as Kubernetes charts](https://github.com/kubernetes/charts) - Find and use [popular software packaged as Helm charts](https://github.com/helm/charts) to run in Kubernetes
- Share your own applications as Kubernetes charts - Share your own applications as Helm charts
- Create reproducible builds of your Kubernetes applications - Create reproducible builds of your Kubernetes applications
- Intelligently manage your Kubernetes manifest files - Intelligently manage your Kubernetes manifest files
- Manage releases of Helm packages - Manage releases of Helm packages
@ -32,14 +32,16 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install ## Install
Binary downloads of the Helm client can be found on [the latest Releases page](https://github.com/kubernetes/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!
If you want to use a package manager: If you want to use a package manager:
- macOS/[homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`. - [Homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`.
- Windows/[chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-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).
@ -52,21 +54,20 @@ Get started with the [Quick Start guide](https://docs.helm.sh/using_helm/#quicks
## Roadmap ## Roadmap
The [Helm roadmap uses Github milestones](https://github.com/kubernetes/helm/milestones) to track the progress of the project. The [Helm roadmap uses Github milestones](https://github.com/helm/helm/milestones) to track the progress of the project.
## Community, discussion, contribution, and support ## Community, discussion, contribution, and support
You can reach the Helm community and developers via the following channels: You can reach the Helm community and developers via the following channels:
- [Kubernetes Slack](http://slack.k8s.io): - [Kubernetes Slack](https://kubernetes.slack.com):
- #helm-users - [#helm-users](https://kubernetes.slack.com/messages/helm-users)
- #helm-dev - [#helm-dev](https://kubernetes.slack.com/messages/helm-dev)
- #charts - [#charts](https://kubernetes.slack.com/messages/charts)
- Mailing Lists: - Mailing List:
- [Helm Mailing List](https://lists.cncf.io/g/cncf-kubernetes-helm) - [Helm Mailing List](https://lists.cncf.io/g/cncf-helm)
- [Kubernetes SIG Apps Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-apps) - Developer Call: Thursdays at 9:30-10:00 Pacific. [https://zoom.us/j/696660622](https://zoom.us/j/696660622)
- Developer Call: Thursdays at 9:30-10:00 Pacific. [https://zoom.us/j/4526666954](https://zoom.us/j/4526666954)
### Code of conduct ### Code of conduct
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md).

@ -0,0 +1,20 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://github.com/helm/helm/blob/master/CONTRIBUTING.md#reporting-a-security-issue
adamreese
bacongobbler
mattfarina
michelleN
prydonius
SlickNik
technosophos
thomastaylor312

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2017 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -89,7 +89,7 @@ service ReleaseService {
// //
// Releases can be retrieved in chunks by setting limit and offset. // Releases can be retrieved in chunks by setting limit and offset.
// //
// Releases can be sorted according to a few pre-determined sort stategies. // Releases can be sorted according to a few pre-determined sort strategies.
message ListReleasesRequest { message ListReleasesRequest {
// Limit is the maximum number of releases to be returned. // Limit is the maximum number of releases to be returned.
int64 limit = 1; int64 limit = 1;
@ -124,6 +124,7 @@ message ListSort{
UNKNOWN = 0; UNKNOWN = 0;
NAME = 1; NAME = 1;
LAST_RELEASED = 2; LAST_RELEASED = 2;
CHART_NAME = 3;
} }
// SortOrder defines sort orders to augment sorting operations. // SortOrder defines sort orders to augment sorting operations.
@ -209,8 +210,10 @@ message UpdateReleaseRequest {
bool reuse_values = 10; bool reuse_values = 10;
// Force resource update through delete/recreate if needed. // Force resource update through delete/recreate if needed.
bool force = 11; bool force = 11;
// Render subchart notes if enabled // Description, if set, will set the description for the updated release
bool subNotes = 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.
@ -236,6 +239,8 @@ message RollbackReleaseRequest {
bool wait = 7; bool wait = 7;
// Force resource update through delete/recreate if needed. // Force resource update through delete/recreate if needed.
bool force = 8; bool force = 8;
// Description, if set, will set the description for the rollback
string description = 9;
} }
// RollbackReleaseResponse is the response to an update request. // RollbackReleaseResponse is the response to an update request.
@ -262,10 +267,10 @@ message InstallReleaseRequest {
// DisableHooks causes the server to skip running any hooks for the install. // DisableHooks causes the server to skip running any hooks for the install.
bool disable_hooks = 5; bool disable_hooks = 5;
// Namepace is the kubernetes namespace of the release. // Namespace is the kubernetes namespace of the release.
string namespace = 6; string namespace = 6;
// ReuseName requests that Tiller re-uses a name, instead of erroring out. // Reuse_name requests that Tiller re-uses a name, instead of erroring out.
bool reuse_name = 7; bool reuse_name = 7;
// 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.
@ -276,7 +281,11 @@ message InstallReleaseRequest {
bool disable_crd_hook = 10; bool disable_crd_hook = 10;
bool subNotes = 11; // Description, if set, will set the description for the installed release
string description = 11;
bool subNotes = 12;
} }
// InstallReleaseResponse is the response from a release installation. // InstallReleaseResponse is the response from a release installation.
@ -294,6 +303,8 @@ 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
string description = 5;
} }
// UninstallReleaseResponse represents a successful response to an uninstall request. // UninstallReleaseResponse represents a successful response to an uninstall request.

@ -1,4 +1,4 @@
// Copyright 2016 The Kubernetes Authors All rights reserved. // Copyright The Helm Authors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@ -81,7 +81,8 @@ func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
} }
func runCompletionZsh(out io.Writer, cmd *cobra.Command) error { func runCompletionZsh(out io.Writer, cmd *cobra.Command) error {
zshInitialization := ` zshInitialization := `#compdef helm
__helm_bash_source() { __helm_bash_source() {
alias shopt=':' alias shopt=':'
alias _expand=_bash_expand alias _expand=_bash_expand

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -38,15 +38,17 @@ something like this:
foo/ foo/
| |
|- .helmignore # Contains patterns to ignore when packaging Helm charts. |- .helmignore # Contains patterns to ignore when packaging Helm charts.
| |
|- Chart.yaml # Information about your chart |- Chart.yaml # Information about your chart
| |
|- values.yaml # The default values for your templates |- values.yaml # The default values for your templates
| |
|- charts/ # Charts that this chart depends on |- charts/ # Charts that this chart depends on
| |
|- templates/ # The template files |- templates/ # The template files
|
|- templates/tests/ # The test files
'helm create' takes a path for an argument. If directories in the given path 'helm create' takes a path for an argument. If directories in the given path
do not exist, Helm will attempt to create them as it goes. If the given do not exist, Helm will attempt to create them as it goes. If the given
@ -84,7 +86,6 @@ func newCreateCmd(out io.Writer) *cobra.Command {
func (c *createCmd) run() error { func (c *createCmd) run() error {
fmt.Fprintf(c.out, "Creating %s\n", c.name) fmt.Fprintf(c.out, "Creating %s\n", c.name)
chartname := filepath.Base(c.name) chartname := filepath.Base(c.name)
cfile := &chart.Metadata{ cfile := &chart.Metadata{
Name: chartname, Name: chartname,

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -143,8 +143,8 @@ func TestCreateStarterCmd(t *testing.T) {
t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion) t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion)
} }
if l := len(c.Templates); l != 6 { if l := len(c.Templates); l != 7 {
t.Errorf("Expected 5 templates, got %d", l) t.Errorf("Expected 6 templates, got %d", l)
} }
found := false found := false

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -40,6 +40,7 @@ type deleteCmd struct {
disableHooks bool disableHooks bool
purge bool purge bool
timeout int64 timeout int64
description string
out io.Writer out io.Writer
client helm.Interface client helm.Interface
@ -77,10 +78,15 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.BoolVar(&del.dryRun, "dry-run", false, "simulate a delete") f.BoolVar(&del.dryRun, "dry-run", false, "simulate a delete")
f.BoolVar(&del.disableHooks, "no-hooks", false, "prevent hooks from running during deletion") f.BoolVar(&del.disableHooks, "no-hooks", false, "prevent hooks from running during deletion")
f.BoolVar(&del.purge, "purge", false, "remove the release from the store and make its name free for later use") f.BoolVar(&del.purge, "purge", false, "remove the release from the store and make its name free for later use")
f.Int64Var(&del.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&del.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.StringVar(&del.description, "description", "", "specify a description for the release")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }
@ -91,6 +97,7 @@ func (d *deleteCmd) run() error {
helm.DeleteDisableHooks(d.disableHooks), helm.DeleteDisableHooks(d.disableHooks),
helm.DeletePurge(d.purge), helm.DeletePurge(d.purge),
helm.DeleteTimeout(d.timeout), helm.DeleteTimeout(d.timeout),
helm.DeleteDescription(d.description),
} }
res, err := d.client.DeleteRelease(d.name, opts...) res, err := d.client.DeleteRelease(d.name, opts...)
if res != nil && res.Info != "" { if res != nil && res.Info != "" {

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -61,6 +61,14 @@ func TestDelete(t *testing.T) {
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
}, },
{
name: "delete with description",
args: []string{"aeneas"},
flags: []string{"--description", "foo"},
expected: "",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
},
{ {
name: "delete without release", name: "delete without release",
args: []string{}, args: []string{},

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@ -141,7 +141,7 @@ func (l *dependencyListCmd) run() error {
r, err := chartutil.LoadRequirements(c) r, err := chartutil.LoadRequirements(c)
if err != nil { if err != nil {
if err == chartutil.ErrRequirementsNotFound { if err == chartutil.ErrRequirementsNotFound {
fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts\n", l.chartpath) fmt.Fprintf(l.out, "WARNING: no requirements at %s\n", filepath.Join(l.chartpath, "charts"))
return nil return nil
} }
return err return err

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -70,11 +70,17 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
}, },
} }
cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the named release with revision")
cmd.AddCommand(addFlagsTLS(newGetValuesCmd(nil, out))) cmd.AddCommand(newGetValuesCmd(nil, out))
cmd.AddCommand(addFlagsTLS(newGetManifestCmd(nil, out))) cmd.AddCommand(newGetManifestCmd(nil, out))
cmd.AddCommand(addFlagsTLS(newGetHooksCmd(nil, out))) cmd.AddCommand(newGetHooksCmd(nil, out))
cmd.AddCommand(newGetNotesCmd(nil, out))
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -57,7 +57,13 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
return ghc.run() return ghc.run()
}, },
} }
cmd.Flags().Int32Var(&ghc.version, "revision", 0, "get the named release with revision") f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&ghc.version, "revision", 0, "get the named release with revision")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -60,7 +60,13 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
}, },
} }
cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the named release with revision")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -0,0 +1,82 @@
/*
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 main
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
)
var getNotesHelp = `
This command shows notes provided by the chart of a named release.
`
type getNotesCmd struct {
release string
out io.Writer
client helm.Interface
version int32
}
func newGetNotesCmd(client helm.Interface, out io.Writer) *cobra.Command {
get := &getNotesCmd{
out: out,
client: client,
}
cmd := &cobra.Command{
Use: "notes [flags] RELEASE_NAME",
Short: "displays the notes of the named release",
Long: getNotesHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired
}
get.release = args[0]
if get.client == nil {
get.client = newClient()
}
return get.run()
},
}
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the notes of the named release with revision")
// set defaults from environment
settings.InitTLS(f)
return cmd
}
func (n *getNotesCmd) run() error {
res, err := n.client.ReleaseStatus(n.release, helm.StatusReleaseVersion(n.version))
if err != nil {
return prettyError(err)
}
if len(res.Info.Status.Notes) > 0 {
fmt.Fprintf(n.out, "NOTES:\n%s\n", res.Info.Status.Notes)
}
return nil
}

@ -0,0 +1,52 @@
/*
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 main
import (
"io"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestGetNotesCmd(t *testing.T) {
tests := []releaseCase{
{
name: "get notes of a deployed release",
args: []string{"flummoxed-chickadee"},
expected: "NOTES:\nrelease notes\n",
rels: []*release.Release{
releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
},
},
{
name: "get notes requires release name arg",
err: true,
},
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newGetNotesCmd(c, out)
})
}

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@ limitations under the License.
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
@ -36,6 +37,7 @@ type getValuesCmd struct {
out io.Writer out io.Writer
client helm.Interface client helm.Interface
version int32 version int32
output string
} }
func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command { func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
@ -58,8 +60,15 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
}, },
} }
cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") f := cmd.Flags()
cmd.Flags().BoolVarP(&get.allValues, "all", "a", false, "dump all (computed) values") settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the named release with revision")
f.BoolVarP(&get.allValues, "all", "a", false, "dump all (computed) values")
f.StringVar(&get.output, "output", "yaml", "output the specified format (json or yaml)")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }
@ -70,20 +79,42 @@ func (g *getValuesCmd) run() error {
return prettyError(err) return prettyError(err)
} }
values, err := chartutil.ReadValues([]byte(res.Release.Config.Raw))
if err != nil {
return err
}
// If the user wants all values, compute the values and return. // If the user wants all values, compute the values and return.
if g.allValues { if g.allValues {
cfg, err := chartutil.CoalesceValues(res.Release.Chart, res.Release.Config) values, err = chartutil.CoalesceValues(res.Release.Chart, res.Release.Config)
if err != nil { if err != nil {
return err return err
} }
cfgStr, err := cfg.YAML()
if err != nil {
return err
}
fmt.Fprintln(g.out, cfgStr)
return nil
} }
fmt.Fprintln(g.out, res.Release.Config.Raw) result, err := formatValues(g.output, values)
if err != nil {
return err
}
fmt.Fprintln(g.out, result)
return nil return nil
} }
func formatValues(format string, values chartutil.Values) (string, error) {
switch format {
case "", "yaml":
out, err := values.YAML()
if err != nil {
return "", err
}
return out, nil
case "json":
out, err := json.Marshal(values)
if err != nil {
return "", fmt.Errorf("Failed to Marshal JSON output: %s", err)
}
return string(out), nil
default:
return "", fmt.Errorf("Unknown output format %q", format)
}
}

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -23,22 +23,53 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
) )
func TestGetValuesCmd(t *testing.T) { func TestGetValuesCmd(t *testing.T) {
releaseWithValues := helm.ReleaseMock(&helm.MockReleaseOptions{
Name: "thomas-guide",
Chart: &chart.Chart{Values: &chart.Config{Raw: `foo2: "bar2"`}},
Config: &chart.Config{Raw: `foo: "bar"`},
})
tests := []releaseCase{ tests := []releaseCase{
{ {
name: "get values with a release", name: "get values with a release",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
args: []string{"thomas-guide"}, args: []string{"thomas-guide"},
expected: "name: \"value\"", expected: "name: value",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})},
}, },
{
name: "get values with json format",
resp: releaseWithValues,
args: []string{"thomas-guide"},
flags: []string{"--output", "json"},
expected: "{\"foo\":\"bar\"}",
rels: []*release.Release{releaseWithValues},
},
{
name: "get all values with json format",
resp: releaseWithValues,
args: []string{"thomas-guide"},
flags: []string{"--all", "--output", "json"},
expected: "{\"foo\":\"bar\",\"foo2\":\"bar2\"}",
rels: []*release.Release{releaseWithValues},
},
{ {
name: "get values requires release name arg", name: "get values requires release name arg",
err: true, err: true,
}, },
{
name: "get values with invalid output format",
resp: releaseWithValues,
args: []string{"thomas-guide"},
flags: []string{"--output", "INVALID_FORMAT"},
rels: []*release.Release{releaseWithValues},
err: true,
},
} }
cmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command { cmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command {
return newGetValuesCmd(c, out) return newGetValuesCmd(c, out)

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -28,10 +28,10 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
// Import to initialize client auth plugins. // Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
helm_env "k8s.io/helm/pkg/helm/environment" helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/portforwarder" "k8s.io/helm/pkg/helm/portforwarder"
@ -40,16 +40,6 @@ import (
) )
var ( var (
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
tlsKeyFile string // path to TLS key file
tlsVerify bool // enable TLS and verify remote certificates
tlsEnable bool // enable TLS
tlsCaCertDefault = "$HELM_HOME/ca.pem"
tlsCertDefault = "$HELM_HOME/cert.pem"
tlsKeyDefault = "$HELM_HOME/key.pem"
tillerTunnel *kube.Tunnel tillerTunnel *kube.Tunnel
settings helm_env.EnvSettings settings helm_env.EnvSettings
) )
@ -71,11 +61,19 @@ Common actions from this point include:
- helm list: list releases of charts - helm list: list releases of charts
Environment: 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-system") $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")
$HELM_TLS_CA_CERT path to TLS CA certificate used to verify the Helm client and Tiller server certificates (default "$HELM_HOME/ca.pem")
$HELM_TLS_CERT path to TLS client certificate file for authenticating to Tiller (default "$HELM_HOME/cert.pem")
$HELM_TLS_KEY path to TLS client key file for authenticating to Tiller (default "$HELM_HOME/key.pem")
$HELM_TLS_VERIFY enable TLS connection between Helm and Tiller and verify Tiller server certificate (default "false")
$HELM_TLS_ENABLE enable TLS connection between Helm and Tiller (default "false")
$HELM_KEY_PASSPHRASE set HELM_KEY_PASSPHRASE to the passphrase of your PGP private key. If set, you will not be prompted for
the passphrase while signing helm charts
` `
func newRootCmd(args []string) *cobra.Command { func newRootCmd(args []string) *cobra.Command {
@ -85,9 +83,21 @@ func newRootCmd(args []string) *cobra.Command {
Long: globalUsage, Long: globalUsage,
SilenceUsage: true, SilenceUsage: true,
PersistentPreRun: func(*cobra.Command, []string) { PersistentPreRun: func(*cobra.Command, []string) {
tlsCaCertFile = os.ExpandEnv(tlsCaCertFile) if settings.TLSCaCertFile == helm_env.DefaultTLSCaCert || settings.TLSCaCertFile == "" {
tlsCertFile = os.ExpandEnv(tlsCertFile) settings.TLSCaCertFile = settings.Home.TLSCaCert()
tlsKeyFile = os.ExpandEnv(tlsKeyFile) } else {
settings.TLSCaCertFile = os.ExpandEnv(settings.TLSCaCertFile)
}
if settings.TLSCertFile == helm_env.DefaultTLSCert || settings.TLSCertFile == "" {
settings.TLSCertFile = settings.Home.TLSCert()
} else {
settings.TLSCertFile = os.ExpandEnv(settings.TLSCertFile)
}
if settings.TLSKeyFile == helm_env.DefaultTLSKeyFile || settings.TLSKeyFile == "" {
settings.TLSKeyFile = settings.Home.TLSKey()
} else {
settings.TLSKeyFile = os.ExpandEnv(settings.TLSKeyFile)
}
}, },
PersistentPostRun: func(*cobra.Command, []string) { PersistentPostRun: func(*cobra.Command, []string) {
teardown() teardown()
@ -113,18 +123,18 @@ func newRootCmd(args []string) *cobra.Command {
newVerifyCmd(out), newVerifyCmd(out),
// release commands // release commands
addFlagsTLS(newDeleteCmd(nil, out)), newDeleteCmd(nil, out),
addFlagsTLS(newGetCmd(nil, out)), newGetCmd(nil, out),
addFlagsTLS(newHistoryCmd(nil, out)), newHistoryCmd(nil, out),
addFlagsTLS(newInstallCmd(nil, out)), newInstallCmd(nil, out),
addFlagsTLS(newListCmd(nil, out)), newListCmd(nil, out),
addFlagsTLS(newRollbackCmd(nil, out)), newRollbackCmd(nil, out),
addFlagsTLS(newStatusCmd(nil, out)), newStatusCmd(nil, out),
addFlagsTLS(newUpgradeCmd(nil, out)), newUpgradeCmd(nil, out),
addFlagsTLS(newReleaseTestCmd(nil, out)), newReleaseTestCmd(nil, out),
addFlagsTLS(newResetCmd(nil, out)), newResetCmd(nil, out),
addFlagsTLS(newVersionCmd(nil, out)), newVersionCmd(nil, out),
newCompletionCmd(out), newCompletionCmd(out),
newHomeCmd(out), newHomeCmd(out),
@ -158,7 +168,12 @@ func init() {
func main() { func main() {
cmd := newRootCmd(os.Args[1:]) cmd := newRootCmd(os.Args[1:])
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
os.Exit(1) switch e := err.(type) {
case pluginError:
os.Exit(e.code)
default:
os.Exit(1)
}
} }
} }
@ -169,18 +184,18 @@ func markDeprecated(cmd *cobra.Command, notice string) *cobra.Command {
func setupConnection() error { func setupConnection() error {
if settings.TillerHost == "" { if settings.TillerHost == "" {
config, client, err := getKubeClient(settings.KubeContext) config, client, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
if err != nil { if err != nil {
return err return err
} }
tunnel, err := portforwarder.New(settings.TillerNamespace, client, config) tillerTunnel, err = portforwarder.New(settings.TillerNamespace, client, config)
if err != nil { if err != nil {
return err return err
} }
settings.TillerHost = fmt.Sprintf("127.0.0.1:%d", tunnel.Local) settings.TillerHost = fmt.Sprintf("127.0.0.1:%d", tillerTunnel.Local)
debug("Created tunnel using local port: '%d'\n", tunnel.Local) debug("Created tunnel using local port: '%d'\n", tillerTunnel.Local)
} }
// Set up the gRPC config. // Set up the gRPC config.
@ -223,8 +238,8 @@ func prettyError(err error) error {
} }
// configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. // configForContext creates a Kubernetes REST client configuration for a given kubeconfig context.
func configForContext(context string) (*rest.Config, error) { func configForContext(context string, kubeconfig string) (*rest.Config, error) {
config, err := kube.GetConfig(context).ClientConfig() config, err := kube.GetConfig(context, kubeconfig).ClientConfig()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err) return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err)
} }
@ -232,8 +247,8 @@ func configForContext(context string) (*rest.Config, error) {
} }
// getKubeClient creates a Kubernetes config and client for a given kubeconfig context. // getKubeClient creates a Kubernetes config and client for a given kubeconfig context.
func getKubeClient(context string) (*rest.Config, kubernetes.Interface, error) { func getKubeClient(context string, kubeconfig string) (*rest.Config, kubernetes.Interface, error) {
config, err := configForContext(context) config, err := configForContext(context, kubeconfig)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -244,21 +259,6 @@ func getKubeClient(context string) (*rest.Config, kubernetes.Interface, error) {
return config, client, nil return config, client, nil
} }
// getInternalKubeClient creates a Kubernetes config and an "internal" client for a given kubeconfig context.
//
// Prefer the similar getKubeClient if you don't need to use such an internal client.
func getInternalKubeClient(context string) (internalclientset.Interface, error) {
config, err := configForContext(context)
if err != nil {
return nil, err
}
client, err := internalclientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("could not get Kubernetes client: %s", err)
}
return client, nil
}
// ensureHelmClient returns a new helm client impl. if h is not nil. // ensureHelmClient returns a new helm client impl. if h is not nil.
func ensureHelmClient(h helm.Interface) helm.Interface { func ensureHelmClient(h helm.Interface) helm.Interface {
if h != nil { if h != nil {
@ -270,20 +270,16 @@ func ensureHelmClient(h helm.Interface) helm.Interface {
func newClient() helm.Interface { func newClient() helm.Interface {
options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)} options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)}
if tlsVerify || tlsEnable { if settings.TLSVerify || settings.TLSEnable {
if tlsCaCertFile == "" { debug("Host=%q, Key=%q, Cert=%q, CA=%q\n", settings.TLSServerName, settings.TLSKeyFile, settings.TLSCertFile, settings.TLSCaCertFile)
tlsCaCertFile = settings.Home.TLSCaCert() tlsopts := tlsutil.Options{
} ServerName: settings.TLSServerName,
if tlsCertFile == "" { KeyFile: settings.TLSKeyFile,
tlsCertFile = settings.Home.TLSCert() CertFile: settings.TLSCertFile,
} InsecureSkipVerify: true,
if tlsKeyFile == "" {
tlsKeyFile = settings.Home.TLSKey()
} }
debug("Key=%q, Cert=%q, CA=%q\n", tlsKeyFile, tlsCertFile, tlsCaCertFile) if settings.TLSVerify {
tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true} tlsopts.CaCertFile = settings.TLSCaCertFile
if tlsVerify {
tlsopts.CaCertFile = tlsCaCertFile
tlsopts.InsecureSkipVerify = false tlsopts.InsecureSkipVerify = false
} }
tlscfg, err := tlsutil.ClientConfig(tlsopts) tlscfg, err := tlsutil.ClientConfig(tlsopts)
@ -295,16 +291,3 @@ func newClient() helm.Interface {
} }
return helm.NewClient(options...) return helm.NewClient(options...)
} }
// addFlagsTLS adds the flags for supporting client side TLS to the
// helm command (only those that invoke communicate to Tiller.)
func addFlagsTLS(cmd *cobra.Command) *cobra.Command {
// add flags
cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file")
cmd.Flags().StringVar(&tlsCertFile, "tls-cert", tlsCertDefault, "path to TLS certificate file")
cmd.Flags().StringVar(&tlsKeyFile, "tls-key", tlsKeyDefault, "path to TLS key file")
cmd.Flags().BoolVar(&tlsVerify, "tls-verify", false, "enable TLS for request and verify remote")
cmd.Flags().BoolVar(&tlsEnable, "tls", false, "enable TLS for request")
return cmd
}

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
@ -229,6 +230,315 @@ func TestRootCmd(t *testing.T) {
} }
} }
func TestTLSFlags(t *testing.T) {
cleanup := resetEnv()
defer cleanup()
homePath := os.Getenv("HELM_HOME")
if homePath == "" {
homePath = filepath.Join(os.Getenv("HOME"), ".helm")
}
home := helmpath.Home(homePath)
tests := []struct {
name string
args []string
envars map[string]string
settings environment.EnvSettings
}{
{
name: "defaults",
args: []string{"version", "-c"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls enable",
args: []string{"version", "-c", "--tls"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: true,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls verify",
args: []string{"version", "-c", "--tls-verify"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: true,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls servername",
args: []string{"version", "-c", "--tls-hostname=foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "foo",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls cacert",
args: []string{"version", "-c", "--tls-ca-cert=/foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: "/foo",
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls cert",
args: []string{"version", "-c", "--tls-cert=/foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: "/foo",
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls key",
args: []string{"version", "-c", "--tls-key=/foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: "/foo",
},
},
{
name: "tls enable envvar",
args: []string{"version", "-c"},
envars: map[string]string{"HELM_TLS_ENABLE": "true"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: true,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls verify envvar",
args: []string{"version", "-c"},
envars: map[string]string{"HELM_TLS_VERIFY": "true"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: true,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls servername envvar",
args: []string{"version", "-c"},
envars: map[string]string{"HELM_TLS_HOSTNAME": "foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "foo",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls cacert envvar",
args: []string{"version", "-c"},
envars: map[string]string{"HELM_TLS_CA_CERT": "/foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: "/foo",
TLSCertFile: home.TLSCert(),
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls cert envvar",
args: []string{"version", "-c"},
envars: map[string]string{"HELM_TLS_CERT": "/foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: "/foo",
TLSKeyFile: home.TLSKey(),
},
},
{
name: "tls key envvar",
args: []string{"version", "-c"},
envars: map[string]string{"HELM_TLS_KEY": "/foo"},
settings: environment.EnvSettings{
TillerHost: "",
TillerConnectionTimeout: 300,
TillerNamespace: "kube-system",
Home: home,
Debug: false,
KubeContext: "",
KubeConfig: "",
TLSEnable: false,
TLSVerify: false,
TLSServerName: "",
TLSCaCertFile: home.TLSCaCert(),
TLSCertFile: home.TLSCert(),
TLSKeyFile: "/foo",
},
},
}
// ensure not set locally
tlsEnvvars := []string{
"HELM_TLS_HOSTNAME",
"HELM_TLS_CA_CERT",
"HELM_TLS_CERT",
"HELM_TLS_KEY",
"HELM_TLS_VERIFY",
"HELM_TLS_ENABLE",
}
for i := range tlsEnvvars {
os.Unsetenv(tlsEnvvars[i])
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for k, v := range tt.envars {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
cmd := newRootCmd(tt.args)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tt.args)
cmd.Run = func(*cobra.Command, []string) {}
if err := cmd.Execute(); err != nil {
t.Errorf("unexpected error: %s", err)
}
if settings != tt.settings {
t.Errorf("expected settings %v, got %v", tt.settings, settings)
}
})
}
}
func resetEnv() func() { func resetEnv() func() {
origSettings := settings origSettings := settings
origEnv := os.Environ() origEnv := os.Environ()

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -88,10 +88,14 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&his.max, "max", 256, "maximum number of revision to include in history") f.Int32Var(&his.max, "max", 256, "maximum number of revision to include in history")
f.UintVar(&his.colWidth, "col-width", 60, "specifies the max column width of output") f.UintVar(&his.colWidth, "col-width", 60, "specifies the max column width of output")
f.StringVarP(&his.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)") f.StringVarP(&his.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -70,6 +70,12 @@ var (
// This is the IPv4 loopback, not localhost, because we have to force IPv4 // This is the IPv4 loopback, not localhost, because we have to force IPv4
// for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410 // for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410
localRepositoryURL = "http://127.0.0.1:8879/charts" localRepositoryURL = "http://127.0.0.1:8879/charts"
tlsServerName string // overrides the server name used to verify the hostname on the returned certificates from the server.
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
tlsKeyFile string // path to TLS key file
tlsVerify bool // enable TLS and verify remote certificates
tlsEnable bool // enable TLS
) )
type initCmd struct { type initCmd struct {
@ -121,11 +127,16 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests") f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests")
// TODO: replace TLS flags with pkg/helm/environment.AddFlagsTLS() in Helm 3
//
// NOTE (bacongobbler): we can't do this in Helm 2 because the flag names differ, and `helm init --tls-ca-cert`
// doesn't conform with the rest of the TLS flag names (should be --tiller-tls-ca-cert in Helm 3)
f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled")
f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates") f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates")
f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller") f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller")
f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller") f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller")
f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate")
f.StringVar(&tlsServerName, "tiller-tls-hostname", settings.TillerHost, "the server name used to verify the hostname on the returned certificates from Tiller")
f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository")
f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository")
@ -138,6 +149,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)")
f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)") f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)")
f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.BoolVar(&i.opts.AutoMountServiceAccountToken, "automount-service-account-token", true, "auto-mount the given service account to tiller")
return cmd return cmd
} }
@ -164,6 +176,14 @@ func (i *initCmd) tlsOptions() error {
return errors.New("missing required TLS CA file") return errors.New("missing required TLS CA file")
} }
} }
// FIXME: remove once we use pkg/helm/environment.AddFlagsTLS() in Helm 3
settings.TLSEnable = tlsEnable
settings.TLSVerify = tlsVerify
settings.TLSServerName = tlsServerName
settings.TLSCaCertFile = tlsCaCertFile
settings.TLSCertFile = tlsCertFile
settings.TLSKeyFile = tlsKeyFile
} }
return nil return nil
} }
@ -181,95 +201,64 @@ func (i *initCmd) run() error {
i.opts.MaxHistory = i.maxHistory i.opts.MaxHistory = i.maxHistory
i.opts.Replicas = i.replicas i.opts.Replicas = i.replicas
writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { writeYAMLManifests := func(manifests []string) error {
w := i.out w := i.out
if !first { for _, manifest := range manifests {
// YAML starting document boundary marker
if _, err := fmt.Fprintln(w, "---"); err != nil { if _, err := fmt.Fprintln(w, "---"); err != nil {
return err return err
} }
if _, err := fmt.Fprintln(w, manifest); err != nil {
return err
}
} }
if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil {
return err
}
if _, err := fmt.Fprintln(w, "kind:", kind); err != nil {
return err
}
if _, err := fmt.Fprint(w, body); err != nil {
return err
}
if !last {
return nil
}
// YAML ending document boundary marker // YAML ending document boundary marker
_, err := fmt.Fprintln(w, "...") _, err := fmt.Fprintln(w, "...")
return err return err
} }
if len(i.opts.Output) > 0 { if len(i.opts.Output) > 0 {
var body string var manifests []string
var err error var err error
const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",` if manifests, err = installer.TillerManifests(&i.opts); err != nil {
if body, err = installer.DeploymentManifest(&i.opts); err != nil {
return err return err
} }
switch i.opts.Output.String() { switch i.opts.Output.String() {
case "json": case "json":
var out bytes.Buffer for _, manifest := range manifests {
jsonb, err := yaml.ToJSON([]byte(body)) var out bytes.Buffer
if err != nil { jsonb, err := yaml.ToJSON([]byte(manifest))
return err if err != nil {
} return err
buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1)) }
buf.WriteString(tm) buf := bytes.NewBuffer(jsonb)
// Drop the opening object delimiter ('{'). if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil {
buf.Write(jsonb[1:]) return err
if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { }
return err if _, err = i.out.Write(out.Bytes()); err != nil {
} return err
if _, err = i.out.Write(out.Bytes()); err != nil { }
return err fmt.Fprint(i.out, "\n")
} }
return nil return nil
case "yaml": case "yaml":
if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { return writeYAMLManifests(manifests)
return err
}
return nil
default: default:
return fmt.Errorf("unknown output format: %q", i.opts.Output) return fmt.Errorf("unknown output format: %q", i.opts.Output)
} }
} }
if settings.Debug { if settings.Debug {
var manifests []string
var body string
var err error var err error
// write Deployment manifest // write Tiller manifests
if body, err = installer.DeploymentManifest(&i.opts); err != nil { if manifests, err = installer.TillerManifests(&i.opts); err != nil {
return err
}
if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil {
return err return err
} }
// write Service manifest if err = writeYAMLManifests(manifests); err != nil {
if body, err = installer.ServiceManifest(i.namespace); err != nil {
return err
}
if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil {
return err return err
} }
// write Secret manifest
if i.opts.EnableTLS {
if body, err = installer.SecretManifest(&i.opts); err != nil {
return err
}
if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil {
return err
}
}
} }
if i.dryRun { if i.dryRun {
@ -289,7 +278,7 @@ func (i *initCmd) run() error {
if !i.clientOnly { if !i.clientOnly {
if i.kubeClient == nil { if i.kubeClient == nil {
_, c, err := getKubeClient(settings.KubeContext) _, c, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
if err != nil { if err != nil {
return fmt.Errorf("could not get kubernetes client: %s", err) return fmt.Errorf("could not get kubernetes client: %s", err)
} }
@ -303,7 +292,7 @@ func (i *initCmd) run() error {
if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil {
return fmt.Errorf("error when upgrading: %s", err) return fmt.Errorf("error when upgrading: %s", err)
} }
if err := i.ping(); err != nil { if err := i.ping(i.opts.SelectImage()); err != nil {
return err return err
} }
fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been upgraded to the current version.") fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been upgraded to the current version.")
@ -312,11 +301,14 @@ func (i *initCmd) run() error {
"(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)")
} }
} else { } else {
fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.\n\n"+ fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.")
"Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ if !tlsVerify {
"For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation") fmt.Fprintln(i.out, "\nPlease note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+
"To prevent this, run `helm init` with the --tiller-tls-verify flag.\n"+
"For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation")
}
} }
if err := i.ping(); err != nil { if err := i.ping(i.opts.SelectImage()); err != nil {
return err return err
} }
} else { } else {
@ -327,13 +319,13 @@ func (i *initCmd) run() error {
return nil return nil
} }
func (i *initCmd) ping() error { func (i *initCmd) ping(image string) error {
if i.wait { if i.wait {
_, kubeClient, err := getKubeClient(settings.KubeContext) _, kubeClient, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
if err != nil { if err != nil {
return err return err
} }
if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout) { if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout, image) {
return fmt.Errorf("tiller was not found. polling deadline exceeded") return fmt.Errorf("tiller was not found. polling deadline exceeded")
} }
@ -465,7 +457,7 @@ func ensureRepoFileFormat(file string, out io.Writer) error {
// want to wait before we call New(). // want to wait before we call New().
// //
// Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false. // Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false.
func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64) bool { func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64, newImage string) bool {
deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C
checkTillerPodTicker := time.NewTicker(500 * time.Millisecond) checkTillerPodTicker := time.NewTicker(500 * time.Millisecond)
doneChan := make(chan bool) doneChan := make(chan bool)
@ -474,8 +466,8 @@ func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeou
go func() { go func() {
for range checkTillerPodTicker.C { for range checkTillerPodTicker.C {
_, err := portforwarder.GetTillerPodName(client.CoreV1(), namespace) image, err := portforwarder.GetTillerPodImage(client.CoreV1(), namespace)
if err == nil { if err == nil && image == newImage {
doneChan <- true doneChan <- true
break break
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -32,11 +33,10 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing" testcore "k8s.io/client-go/testing"
"encoding/json"
"k8s.io/helm/cmd/helm/installer" "k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
@ -306,7 +306,7 @@ func TestInitCmd_tlsOptions(t *testing.T) {
} }
} }
// TestInitCmd_output tests that init -o formats are unmarshal-able // TestInitCmd_output tests that init -o can be decoded
func TestInitCmd_output(t *testing.T) { func TestInitCmd_output(t *testing.T) {
// This is purely defensive in this case. // This is purely defensive in this case.
home, err := ioutil.TempDir("", "helm_home") home, err := ioutil.TempDir("", "helm_home")
@ -320,26 +320,14 @@ func TestInitCmd_output(t *testing.T) {
settings.Debug = dbg settings.Debug = dbg
}() }()
fc := fake.NewSimpleClientset() fc := fake.NewSimpleClientset()
tests := []struct { tests := []string{"json", "yaml"}
expectF func([]byte, interface{}) error
expectName string
}{
{
json.Unmarshal,
"json",
},
{
yaml.Unmarshal,
"yaml",
},
}
for _, s := range tests { for _, s := range tests {
var buf bytes.Buffer var buf bytes.Buffer
cmd := &initCmd{ cmd := &initCmd{
out: &buf, out: &buf,
home: helmpath.Home(home), home: helmpath.Home(home),
kubeClient: fc, kubeClient: fc,
opts: installer.Options{Output: installer.OutputFormat(s.expectName)}, opts: installer.Options{Output: installer.OutputFormat(s)},
namespace: v1.NamespaceDefault, namespace: v1.NamespaceDefault,
} }
if err := cmd.run(); err != nil { if err := cmd.run(); err != nil {
@ -348,10 +336,17 @@ func TestInitCmd_output(t *testing.T) {
if got := len(fc.Actions()); got != 0 { if got := len(fc.Actions()); got != 0 {
t.Errorf("expected no server calls, got %d", got) t.Errorf("expected no server calls, got %d", got)
} }
d := &v1beta1.Deployment{}
if err = s.expectF(buf.Bytes(), &d); err != nil { var obj interface{}
t.Errorf("error unmarshalling init %s output %s %s", s.expectName, err, buf.String()) decoder := yamlutil.NewYAMLOrJSONDecoder(&buf, 4096)
for {
err := decoder.Decode(&obj)
if err != nil {
if err == io.EOF {
break
}
t.Errorf("error decoding init %s output %s %s", s, err, buf.String())
}
} }
} }
} }

@ -1,7 +1,7 @@
// +build !windows // +build !windows
/* /*
Copyright 2017 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,7 +1,7 @@
// +build windows // +build windows
/* /*
Copyright 2017 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -37,8 +37,8 @@ import (
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
"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/renderutil"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/strvals" "k8s.io/helm/pkg/strvals"
) )
@ -50,8 +50,10 @@ The install argument must be a chart reference, a path to a packaged chart,
a path to an unpacked chart directory or a URL. a path to an unpacked chart directory or a URL.
To override values in a chart, use either the '--values' flag and pass in a file To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line, to force or use the '--set' flag and pass configuration from the command line. To force string
a string value use '--set-string'. values in '--set', use '--set-string' instead. In case a value is large and therefore
you want not to use neither '--values' nor '--set', use '--set-file' to read the
single large value from file.
$ helm install -f myvalues.yaml ./redis $ helm install -f myvalues.yaml ./redis
@ -63,6 +65,9 @@ or
$ helm install --set-string long_int=1234567890 ./redis $ helm install --set-string long_int=1234567890 ./redis
or
$ helm install --set-file multiline_text=path/to/textfile
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
contained a key called 'Test', the value set in override.yaml would take precedence: contained a key called 'Test', the value set in override.yaml would take precedence:
@ -120,6 +125,7 @@ type installCmd struct {
client helm.Interface client helm.Interface
values []string values []string
stringValues []string stringValues []string
fileValues []string
nameTemplate string nameTemplate string
version string version string
timeout int64 timeout int64
@ -130,6 +136,7 @@ type installCmd struct {
devel bool devel bool
depUp bool depUp bool
subNotes bool subNotes bool
description string
certFile string certFile string
keyFile string keyFile string
@ -187,6 +194,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(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. Defaults to the current kube config namespace.") f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.")
@ -196,6 +204,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
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")
f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&inst.fileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it")
f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
@ -211,6 +220,10 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
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.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")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }
@ -222,7 +235,7 @@ func (i *installCmd) run() error {
i.namespace = defaultNamespace() i.namespace = defaultNamespace()
} }
rawVals, err := vals(i.valueFiles, i.values, i.stringValues, i.certFile, i.keyFile, i.caFile) rawVals, err := vals(i.valueFiles, i.values, i.stringValues, i.fileValues, i.certFile, i.keyFile, i.caFile)
if err != nil { if err != nil {
return err return err
} }
@ -247,7 +260,7 @@ func (i *installCmd) run() error {
// If checkDependencies returns an error, we have unfulfilled dependencies. // If checkDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition: // As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/kubernetes/helm/issues/2209 // https://github.com/kubernetes/helm/issues/2209
if err := checkDependencies(chartRequested, req); err != nil { if err := renderutil.CheckDependencies(chartRequested, req); err != nil {
if i.depUp { if i.depUp {
man := &downloader.Manager{ man := &downloader.Manager{
Out: i.out, Out: i.out,
@ -260,6 +273,12 @@ func (i *installCmd) run() error {
if err := man.Update(); err != nil { if err := man.Update(); err != nil {
return prettyError(err) return prettyError(err)
} }
// Update all dependencies which are present in /charts.
chartRequested, err = chartutil.Load(i.chartPath)
if err != nil {
return prettyError(err)
}
} else { } else {
return prettyError(err) return prettyError(err)
} }
@ -280,7 +299,8 @@ func (i *installCmd) run() error {
helm.InstallDisableCRDHook(i.disableCRDHook), helm.InstallDisableCRDHook(i.disableCRDHook),
helm.InstallSubNotes(i.subNotes), helm.InstallSubNotes(i.subNotes),
helm.InstallTimeout(i.timeout), helm.InstallTimeout(i.timeout),
helm.InstallWait(i.wait)) helm.InstallWait(i.wait),
helm.InstallDescription(i.description))
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
} }
@ -337,8 +357,8 @@ func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[st
} }
// vals merges values from files specified via -f/--values and // vals merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML // directly via --set or --set-string or --set-file, marshaling them to YAML
func vals(valueFiles valueFiles, values []string, stringValues []string, CertFile, KeyFile, CAFile string) ([]byte, error) { func vals(valueFiles valueFiles, values []string, stringValues []string, fileValues []string, CertFile, KeyFile, CAFile string) ([]byte, error) {
base := map[string]interface{}{} base := map[string]interface{}{}
// User specified a values files via -f/--values // User specified a values files via -f/--values
@ -378,6 +398,17 @@ func vals(valueFiles valueFiles, values []string, stringValues []string, CertFil
} }
} }
// User specified a value via --set-file
for _, value := range fileValues {
reader := func(rs []rune) (interface{}, error) {
bytes, err := readFile(string(rs), CertFile, KeyFile, CAFile)
return string(bytes), err
}
if err := strvals.ParseIntoFile(value, base, reader); err != nil {
return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err)
}
}
return yaml.Marshal(base) return yaml.Marshal(base)
} }
@ -485,35 +516,12 @@ func generateName(nameTemplate string) (string, error) {
} }
func defaultNamespace() string { func defaultNamespace() string {
if ns, _, err := kube.GetConfig(settings.KubeContext).Namespace(); err == nil { if ns, _, err := kube.GetConfig(settings.KubeContext, settings.KubeConfig).Namespace(); err == nil {
return ns return ns
} }
return "default" return "default"
} }
func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error {
missing := []string{}
deps := ch.GetDependencies()
for _, r := range reqs.Dependencies {
found := false
for _, d := range deps {
if d.Metadata.Name == r.Name {
found = true
break
}
}
if !found {
missing = append(missing, r.Name)
}
}
if len(missing) > 0 {
return fmt.Errorf("found in requirements.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
}
return nil
}
//readFile load a file from the local directory or a remote file with a url. //readFile load a file from the local directory or a remote file with a url.
func readFile(filePath, CertFile, KeyFile, CAFile string) ([]byte, error) { func readFile(filePath, CertFile, KeyFile, CAFile string) ([]byte, error) {
u, _ := url.Parse(filePath) u, _ := url.Parse(filePath)

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -115,6 +115,13 @@ func TestInstall(t *testing.T) {
expected: "FOOBAR", expected: "FOOBAR",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "FOOBAR"}), resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "FOOBAR"}),
}, },
{
name: "install with custom description",
args: []string{"testdata/testcharts/alpine"},
flags: []string{"--name", "virgil", "--description", "foobar"},
expected: "virgil",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil", Description: "foobar"}),
},
// Install, perform chart verification along the way. // Install, perform chart verification along the way.
{ {
name: "install with verification, missing provenance", name: "install with verification, missing provenance",

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import (
"k8s.io/api/extensions/v1beta1" "k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
@ -67,7 +68,7 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade { if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
return errors.New("current Tiller version is newer, use --force-upgrade to downgrade") return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
} }
obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage() obj.Spec.Template.Spec.Containers[0].Image = opts.SelectImage()
obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy() obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil { if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
@ -104,7 +105,7 @@ func semverCompare(image string) int {
// createDeployment creates the Tiller Deployment resource. // createDeployment creates the Tiller Deployment resource.
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj, err := deployment(opts) obj, err := generateDeployment(opts)
if err != nil { if err != nil {
return err return err
} }
@ -113,40 +114,68 @@ func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options)
} }
// deployment gets the deployment object that installs Tiller. // Deployment gets a deployment object that can be used to generate a manifest
func deployment(opts *Options) (*v1beta1.Deployment, error) { // as a string. This object should not be submitted directly to the Kubernetes
return generateDeployment(opts) // api
func Deployment(opts *Options) (*v1beta1.Deployment, error) {
dep, err := generateDeployment(opts)
if err != nil {
return nil, err
}
dep.TypeMeta = metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "extensions/v1beta1",
}
return dep, nil
} }
// createService creates the Tiller service resource // createService creates the Tiller service resource
func createService(client corev1.ServicesGetter, namespace string) error { func createService(client corev1.ServicesGetter, namespace string) error {
obj := service(namespace) obj := generateService(namespace)
_, err := client.Services(obj.Namespace).Create(obj) _, err := client.Services(obj.Namespace).Create(obj)
return err return err
} }
// service gets the service object that installs Tiller. // Service gets a service object that can be used to generate a manifest as a
func service(namespace string) *v1.Service { // string. This object should not be submitted directly to the Kubernetes api
return generateService(namespace) func Service(namespace string) *v1.Service {
svc := generateService(namespace)
svc.TypeMeta = metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
}
return svc
} }
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment // TillerManifests gets the Deployment, Service, and Secret (if tls-enabled) manifests
// resource. func TillerManifests(opts *Options) ([]string, error) {
func DeploymentManifest(opts *Options) (string, error) { dep, err := Deployment(opts)
obj, err := deployment(opts)
if err != nil { if err != nil {
return "", err return []string{}, err
}
svc := Service(opts.Namespace)
objs := []runtime.Object{dep, svc}
if opts.EnableTLS {
secret, err := Secret(opts)
if err != nil {
return []string{}, err
}
objs = append(objs, secret)
}
manifests := make([]string, len(objs))
for i, obj := range objs {
o, err := yaml.Marshal(obj)
if err != nil {
return []string{}, err
}
manifests[i] = string(o)
} }
buf, err := yaml.Marshal(obj)
return string(buf), err
}
// ServiceManifest gets the manifest (as a string) that describes the Tiller Service return manifests, err
// resource.
func ServiceManifest(namespace string) (string, error) {
obj := service(namespace)
buf, err := yaml.Marshal(obj)
return string(buf), err
} }
func generateLabels(labels map[string]string) map[string]string { func generateLabels(labels map[string]string) map[string]string {
@ -189,11 +218,12 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
Labels: labels, Labels: labels,
}, },
Spec: v1.PodSpec{ Spec: v1.PodSpec{
ServiceAccountName: opts.ServiceAccount, ServiceAccountName: opts.ServiceAccount,
AutomountServiceAccountToken: &opts.AutoMountServiceAccountToken,
Containers: []v1.Container{ Containers: []v1.Container{
{ {
Name: "tiller", Name: "tiller",
Image: opts.selectImage(), Image: opts.SelectImage(),
ImagePullPolicy: opts.pullPolicy(), ImagePullPolicy: opts.pullPolicy(),
Ports: []v1.ContainerPort{ Ports: []v1.ContainerPort{
{ContainerPort: 44134, Name: "tiller"}, {ContainerPort: 44134, Name: "tiller"},
@ -321,14 +351,20 @@ func generateService(namespace string) *v1.Service {
return s return s
} }
// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource. // Secret gets a secret object that can be used to generate a manifest as a
func SecretManifest(opts *Options) (string, error) { // string. This object should not be submitted directly to the Kubernetes api
o, err := generateSecret(opts) func Secret(opts *Options) (*v1.Secret, error) {
secret, err := generateSecret(opts)
if err != nil { if err != nil {
return "", err return nil, err
}
secret.TypeMeta = metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
} }
buf, err := yaml.Marshal(o)
return string(buf), err return secret, nil
} }
// createSecret creates the Tiller secret resource. // createSecret creates the Tiller secret resource.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -34,7 +34,7 @@ import (
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
func TestDeploymentManifest(t *testing.T) { func TestDeployment(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
image string image string
@ -48,14 +48,10 @@ func TestDeploymentManifest(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary}) dep, err := Deployment(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary})
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
var dep v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &dep); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
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)
@ -71,7 +67,7 @@ func TestDeploymentManifest(t *testing.T) {
} }
} }
func TestDeploymentManifestForServiceAccount(t *testing.T) { func TestDeploymentForServiceAccount(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
image string image string
@ -84,22 +80,31 @@ func TestDeploymentManifestForServiceAccount(t *testing.T) {
{"withoutSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", ""}, {"withoutSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", ""},
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount}) opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount}
d, err := Deployment(opts)
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount { if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount {
t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got) t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got)
} }
if got := *d.Spec.Template.Spec.AutomountServiceAccountToken; got != false {
t.Errorf("%s: expected AutomountServiceAccountToken = %t, got %t", tt.name, false, got)
}
opts.AutoMountServiceAccountToken = true
d, err = Deployment(opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
if got := *d.Spec.Template.Spec.AutomountServiceAccountToken; got != true {
t.Errorf("%s: expected AutomountServiceAccountToken = %t, got %t", tt.name, true, got)
}
} }
} }
func TestDeploymentManifest_WithTLS(t *testing.T) { func TestDeployment_WithTLS(t *testing.T) {
tests := []struct { tests := []struct {
opts Options opts Options
name string name string
@ -126,15 +131,11 @@ func TestDeploymentManifest_WithTLS(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts) d, err := Deployment(&tt.opts)
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
// verify environment variable in deployment reflect the use of tls being enabled. // verify environment variable in deployment reflect the use of tls being enabled.
if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.verify { if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.verify {
t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got) t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got)
@ -146,14 +147,7 @@ func TestDeploymentManifest_WithTLS(t *testing.T) {
} }
func TestServiceManifest(t *testing.T) { func TestServiceManifest(t *testing.T) {
o, err := ServiceManifest(v1.NamespaceDefault) svc := Service(v1.NamespaceDefault)
if err != nil {
t.Fatalf("error %q", err)
}
var svc v1.Service
if err := yaml.Unmarshal([]byte(o), &svc); err != nil {
t.Fatalf("error %q", err)
}
if got := svc.ObjectMeta.Namespace; got != v1.NamespaceDefault { if got := svc.ObjectMeta.Namespace; got != v1.NamespaceDefault {
t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got) t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got)
@ -161,7 +155,7 @@ func TestServiceManifest(t *testing.T) {
} }
func TestSecretManifest(t *testing.T) { func TestSecretManifest(t *testing.T) {
o, err := SecretManifest(&Options{ obj, err := Secret(&Options{
VerifyTLS: true, VerifyTLS: true,
EnableTLS: true, EnableTLS: true,
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
@ -174,11 +168,6 @@ func TestSecretManifest(t *testing.T) {
t.Fatalf("error %q", err) t.Fatalf("error %q", err)
} }
var obj v1.Secret
if err := yaml.Unmarshal([]byte(o), &obj); err != nil {
t.Fatalf("error %q", err)
}
if got := obj.ObjectMeta.Namespace; got != v1.NamespaceDefault { if got := obj.ObjectMeta.Namespace; got != v1.NamespaceDefault {
t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got) t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got)
} }
@ -362,13 +351,13 @@ func TestInstall_canary(t *testing.T) {
func TestUpgrade(t *testing.T) { func TestUpgrade(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := generateDeployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v1.0.0", ImageSpec: "imageToReplace:v1.0.0",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
UseCanary: false, UseCanary: false,
}) })
existingService := service(v1.NamespaceDefault) existingService := generateService(v1.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
@ -403,7 +392,7 @@ func TestUpgrade(t *testing.T) {
func TestUpgrade_serviceNotFound(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := generateDeployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace", ImageSpec: "imageToReplace",
UseCanary: false, UseCanary: false,
@ -446,13 +435,13 @@ func TestUpgrade_serviceNotFound(t *testing.T) {
func TestUgrade_newerVersion(t *testing.T) { func TestUgrade_newerVersion(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := generateDeployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v100.5.0", ImageSpec: "imageToReplace:v100.5.0",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
UseCanary: false, UseCanary: false,
}) })
existingService := service(v1.NamespaceDefault) existingService := generateService(v1.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
@ -506,13 +495,13 @@ func TestUgrade_newerVersion(t *testing.T) {
func TestUpgrade_identical(t *testing.T) { func TestUpgrade_identical(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := generateDeployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v2.0.0", ImageSpec: "imageToReplace:v2.0.0",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
UseCanary: false, UseCanary: false,
}) })
existingService := service(v1.NamespaceDefault) existingService := generateService(v1.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
@ -547,13 +536,13 @@ func TestUpgrade_identical(t *testing.T) {
func TestUpgrade_canaryClient(t *testing.T) { func TestUpgrade_canaryClient(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:canary" image := "gcr.io/kubernetes-helm/tiller:canary"
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := generateDeployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v1.0.0", ImageSpec: "imageToReplace:v1.0.0",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
UseCanary: false, UseCanary: false,
}) })
existingService := service(v1.NamespaceDefault) existingService := generateService(v1.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
@ -588,13 +577,13 @@ func TestUpgrade_canaryClient(t *testing.T) {
func TestUpgrade_canaryServer(t *testing.T) { func TestUpgrade_canaryServer(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := generateDeployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:canary", ImageSpec: "imageToReplace:canary",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
UseCanary: false, UseCanary: false,
}) })
existingService := service(v1.NamespaceDefault) existingService := generateService(v1.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
@ -634,7 +623,8 @@ func tlsTestFile(t *testing.T, path string) string {
} }
return path return path
} }
func TestDeploymentManifest_WithNodeSelectors(t *testing.T) {
func TestDeployment_WithNodeSelectors(t *testing.T) {
tests := []struct { tests := []struct {
opts Options opts Options
name string name string
@ -658,15 +648,11 @@ func TestDeploymentManifest_WithNodeSelectors(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts) d, err := Deployment(&tt.opts)
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
// Verify that environment variables in Deployment reflect the use of TLS being enabled. // Verify that environment variables in Deployment reflect the use of TLS being enabled.
got := d.Spec.Template.Spec.NodeSelector got := d.Spec.Template.Spec.NodeSelector
for k, v := range tt.expect { for k, v := range tt.expect {
@ -676,7 +662,8 @@ func TestDeploymentManifest_WithNodeSelectors(t *testing.T) {
} }
} }
} }
func TestDeploymentManifest_WithSetValues(t *testing.T) {
func TestDeployment_WithSetValues(t *testing.T) {
tests := []struct { tests := []struct {
opts Options opts Options
name string name string
@ -703,11 +690,17 @@ func TestDeploymentManifest_WithSetValues(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts) d, err := Deployment(&tt.opts)
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
values, err := chartutil.ReadValues([]byte(o))
o, err := yaml.Marshal(d)
if err != nil {
t.Errorf("Error marshaling Deployment: %s", err)
}
values, err := chartutil.ReadValues(o)
if err != nil { if err != nil {
t.Errorf("Error converting Deployment manifest to Values: %s", err) t.Errorf("Error converting Deployment manifest to Values: %s", err)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -47,10 +47,13 @@ type Options struct {
// ServiceAccount is the Kubernetes service account to add to Tiller. // ServiceAccount is the Kubernetes service account to add to Tiller.
ServiceAccount string ServiceAccount string
// AutoMountServiceAccountToken determines whether or not the service account should be added to Tiller.
AutoMountServiceAccountToken bool
// Force allows to force upgrading tiller if deployed version is greater than current version // Force allows to force upgrading tiller if deployed version is greater than current version
ForceUpgrade bool ForceUpgrade bool
// ImageSpec indentifies the image Tiller will use when deployed. // ImageSpec identifies the image Tiller will use when deployed.
// //
// Valid if and only if UseCanary is false. // Valid if and only if UseCanary is false.
ImageSpec string ImageSpec string
@ -96,7 +99,8 @@ type Options struct {
Values []string Values []string
} }
func (opts *Options) selectImage() string { // SelectImage returns the image according to whether UseCanary is true or not
func (opts *Options) SelectImage() string {
switch { switch {
case opts.UseCanary: case opts.UseCanary:
return defaultImage + ":canary" return defaultImage + ":canary"

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -19,10 +19,8 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl"
) )
const ( const (
@ -32,7 +30,7 @@ const (
) )
// Uninstall uses Kubernetes client to uninstall Tiller. // Uninstall uses Kubernetes client to uninstall Tiller.
func Uninstall(client internalclientset.Interface, opts *Options) error { func Uninstall(client kubernetes.Interface, opts *Options) error {
if err := deleteService(client.Core(), opts.Namespace); err != nil { if err := deleteService(client.Core(), opts.Namespace); err != nil {
return err return err
} }
@ -43,7 +41,7 @@ func Uninstall(client internalclientset.Interface, opts *Options) error {
} }
// deleteService deletes the Tiller Service resource // deleteService deletes the Tiller Service resource
func deleteService(client coreclient.ServicesGetter, namespace string) error { func deleteService(client corev1.ServicesGetter, namespace string) error {
err := client.Services(namespace).Delete(serviceName, &metav1.DeleteOptions{}) err := client.Services(namespace).Delete(serviceName, &metav1.DeleteOptions{})
return ingoreNotFound(err) return ingoreNotFound(err)
} }
@ -51,14 +49,13 @@ func deleteService(client coreclient.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 // 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) // is not yet supported at the k8s server level (<= 1.5)
func deleteDeployment(client internalclientset.Interface, namespace string) error { func deleteDeployment(client kubernetes.Interface, namespace string) error {
reaper, _ := kubectl.ReaperFor(extensions.Kind("Deployment"), client) err := client.Extensions().Deployments(namespace).Delete(deploymentName, &metav1.DeleteOptions{})
err := reaper.Stop(namespace, deploymentName, 0, nil)
return ingoreNotFound(err) return ingoreNotFound(err)
} }
// deleteSecret deletes the Tiller Secret resource // deleteSecret deletes the Tiller Secret resource
func deleteSecret(client coreclient.SecretsGetter, namespace string) error { func deleteSecret(client corev1.SecretsGetter, namespace string) error {
err := client.Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) err := client.Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{})
return ingoreNotFound(err) return ingoreNotFound(err)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -19,23 +19,23 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"testing" "testing"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing" testcore "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
) )
func TestUninstall(t *testing.T) { func TestUninstall(t *testing.T) {
fc := &fake.Clientset{} fc := &fake.Clientset{}
opts := &Options{Namespace: core.NamespaceDefault} opts := &Options{Namespace: v1.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil { if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err) t.Errorf("unexpected error: %#+v", err)
} }
if actions := fc.Actions(); len(actions) != 7 { if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions)) t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
} }
} }
@ -45,44 +45,44 @@ func TestUninstall_serviceNotFound(t *testing.T) {
return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "services"}, "1") return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "services"}, "1")
}) })
opts := &Options{Namespace: core.NamespaceDefault} opts := &Options{Namespace: v1.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil { if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err) t.Errorf("unexpected error: %#+v", err)
} }
if actions := fc.Actions(); len(actions) != 7 { if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions)) t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
} }
} }
func TestUninstall_deploymentNotFound(t *testing.T) { func TestUninstall_deploymentNotFound(t *testing.T) {
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("delete", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("delete", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(core.Resource("deployments"), "1") return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "deployments"}, "1")
}) })
opts := &Options{Namespace: core.NamespaceDefault} opts := &Options{Namespace: v1.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil { if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err) t.Errorf("unexpected error: %#+v", err)
} }
if actions := fc.Actions(); len(actions) != 7 { if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions)) t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
} }
} }
func TestUninstall_secretNotFound(t *testing.T) { func TestUninstall_secretNotFound(t *testing.T) {
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("delete", "secrets", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("delete", "secrets", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(core.Resource("secrets"), "1") return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "secrets"}, "1")
}) })
opts := &Options{Namespace: core.NamespaceDefault} opts := &Options{Namespace: v1.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil { if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err) t.Errorf("unexpected error: %#+v", err)
} }
if actions := fc.Actions(); len(actions) != 7 { if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expect 7 actions got %d", actions, len(actions)) t.Errorf("unexpected actions: %v, expect 3 actions got %d", actions, len(actions))
} }
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -47,6 +47,7 @@ type lintCmd struct {
valueFiles valueFiles valueFiles valueFiles
values []string values []string
sValues []string sValues []string
fValues []string
namespace string namespace string
strict bool strict bool
paths []string paths []string
@ -73,7 +74,8 @@ func newLintCmd(out io.Writer) *cobra.Command {
cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)") cmd.Flags().StringArrayVar(&l.fValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to put the release into")
cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings") cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings")
return cmd return cmd
@ -172,6 +174,12 @@ func lintChart(path string, vals []byte, namespace string, strict bool) (support
return lint.All(chartPath, vals, namespace, strict), nil return lint.All(chartPath, vals, namespace, strict), nil
} }
// vals merges values from files specified via -f/--values and
// directly via --set or --set-string or --set-file, marshaling them to YAML
//
// This func is implemented intentionally and separately from the `vals` func for the `install` and `upgrade` comammdsn.
// Compared to the alternative func, this func lacks the parameters for tls opts - ca key, cert, and ca cert.
// That's because this command, `lint`, is explicitly forbidden from making server connections.
func (l *lintCmd) vals() ([]byte, error) { func (l *lintCmd) vals() ([]byte, error) {
base := map[string]interface{}{} base := map[string]interface{}{}
@ -204,5 +212,16 @@ func (l *lintCmd) vals() ([]byte, error) {
} }
} }
// User specified a value via --set-file
for _, value := range l.fValues {
reader := func(rs []rune) (interface{}, error) {
bytes, err := ioutil.ReadFile(string(rs))
return string(bytes), err
}
if err := strvals.ParseIntoFile(value, base, reader); err != nil {
return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err)
}
}
return yaml.Marshal(base) return yaml.Marshal(base)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -17,10 +17,12 @@ limitations under the License.
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"strings" "strings"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -58,23 +60,40 @@ flag with the '--offset' flag allows you to page through results.
` `
type listCmd struct { type listCmd struct {
filter string filter string
short bool short bool
limit int limit int
offset string offset string
byDate bool byDate bool
sortDesc bool sortDesc bool
out io.Writer out io.Writer
all bool all bool
deleted bool deleted bool
deleting bool deleting bool
deployed bool deployed bool
failed bool failed bool
namespace string namespace string
superseded bool superseded bool
pending bool pending bool
client helm.Interface client helm.Interface
colWidth uint colWidth uint
output string
byChartName bool
}
type listResult struct {
Next string
Releases []listRelease
}
type listRelease struct {
Name string
Revision int32
Updated string
Status string
Chart string
AppVersion string
Namespace string
} }
func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
@ -101,6 +120,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.BoolVarP(&list.short, "short", "q", false, "output short (quiet) listing format") f.BoolVarP(&list.short, "short", "q", false, "output short (quiet) listing format")
f.BoolVarP(&list.byDate, "date", "d", false, "sort by release date") f.BoolVarP(&list.byDate, "date", "d", false, "sort by release date")
f.BoolVarP(&list.sortDesc, "reverse", "r", false, "reverse the sort order") f.BoolVarP(&list.sortDesc, "reverse", "r", false, "reverse the sort order")
@ -114,10 +134,15 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&list.pending, "pending", false, "show pending releases") f.BoolVar(&list.pending, "pending", false, "show pending releases")
f.StringVar(&list.namespace, "namespace", "", "show releases within a specific namespace") f.StringVar(&list.namespace, "namespace", "", "show releases within a specific namespace")
f.UintVar(&list.colWidth, "col-width", 60, "specifies the max column width of output") f.UintVar(&list.colWidth, "col-width", 60, "specifies the max column width of output")
f.StringVar(&list.output, "output", "", "output the specified format (json or yaml)")
f.BoolVarP(&list.byChartName, "chart-name", "c", false, "sort by chart name")
// TODO: Do we want this as a feature of 'helm list'? // TODO: Do we want this as a feature of 'helm list'?
//f.BoolVar(&list.superseded, "history", true, "show historical releases") //f.BoolVar(&list.superseded, "history", true, "show historical releases")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }
@ -126,6 +151,9 @@ func (l *listCmd) run() error {
if l.byDate { if l.byDate {
sortBy = services.ListSort_LAST_RELEASED sortBy = services.ListSort_LAST_RELEASED
} }
if l.byChartName {
sortBy = services.ListSort_CHART_NAME
}
sortOrder := services.ListSort_ASC sortOrder := services.ListSort_ASC
if l.sortDesc { if l.sortDesc {
@ -147,24 +175,21 @@ func (l *listCmd) run() error {
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
} }
if res == nil {
if len(res.GetReleases()) == 0 {
return nil return nil
} }
if res.Next != "" && !l.short { rels := filterList(res.GetReleases())
fmt.Fprintf(l.out, "\tnext: %s\n", res.Next)
}
rels := filterList(res.Releases) result := getListResult(rels, res.Next)
if l.short { output, err := formatResult(l.output, l.short, result, l.colWidth)
for _, r := range rels {
fmt.Fprintln(l.out, r.Name) if err != nil {
} return prettyError(err)
return nil
} }
fmt.Fprintln(l.out, formatList(rels, l.colWidth))
fmt.Fprintln(l.out, output)
return nil return nil
} }
@ -233,23 +258,98 @@ func (l *listCmd) statusCodes() []release.Status_Code {
return status return status
} }
func formatList(rels []*release.Release, colWidth uint) string { func getListResult(rels []*release.Release, next string) listResult {
table := uitable.New() listReleases := []listRelease{}
table.MaxColWidth = colWidth
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "NAMESPACE")
for _, r := range rels { for _, r := range rels {
md := r.GetChart().GetMetadata() md := r.GetChart().GetMetadata()
c := fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion())
t := "-" t := "-"
if tspb := r.GetInfo().GetLastDeployed(); tspb != nil { if tspb := r.GetInfo().GetLastDeployed(); tspb != nil {
t = timeconv.String(tspb) t = timeconv.String(tspb)
} }
s := r.GetInfo().GetStatus().GetCode().String()
v := r.GetVersion() lr := listRelease{
a := md.GetAppVersion() Name: r.GetName(),
n := r.GetNamespace() Revision: r.GetVersion(),
table.AddRow(r.GetName(), v, t, s, c, a, n) Updated: t,
Status: r.GetInfo().GetStatus().GetCode().String(),
Chart: fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion()),
AppVersion: md.GetAppVersion(),
Namespace: r.GetNamespace(),
}
listReleases = append(listReleases, lr)
} }
return table.String()
return listResult{
Releases: listReleases,
Next: next,
}
}
func shortenListResult(result listResult) []string {
names := []string{}
for _, r := range result.Releases {
names = append(names, r.Name)
}
return names
}
func formatResult(format string, short bool, result listResult, colWidth uint) (string, error) {
var output string
var err error
var shortResult []string
var finalResult interface{}
if short {
shortResult = shortenListResult(result)
finalResult = shortResult
} else {
finalResult = result
}
switch format {
case "":
if short {
output = formatTextShort(shortResult)
} else {
output = formatText(result, colWidth)
}
case "json":
o, e := json.Marshal(finalResult)
if e != nil {
err = fmt.Errorf("Failed to Marshal JSON output: %s", e)
} else {
output = string(o)
}
case "yaml":
o, e := yaml.Marshal(finalResult)
if e != nil {
err = fmt.Errorf("Failed to Marshal YAML output: %s", e)
} else {
output = string(o)
}
default:
err = fmt.Errorf("Unknown output format \"%s\"", format)
}
return output, err
}
func formatText(result listResult, colWidth uint) string {
nextOutput := ""
if result.Next != "" {
nextOutput = fmt.Sprintf("\tnext: %s\n", result.Next)
}
table := uitable.New()
table.MaxColWidth = colWidth
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "NAMESPACE")
for _, lr := range result.Releases {
table.AddRow(lr.Name, lr.Revision, lr.Updated, lr.Status, lr.Chart, lr.AppVersion, lr.Namespace)
}
return fmt.Sprintf("%s%s", nextOutput, table.String())
}
func formatTextShort(shortResult []string) string {
return strings.Join(shortResult, "\n")
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,16 +18,18 @@ package main
import ( import (
"io" "io"
"regexp"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"io/ioutil" "io/ioutil"
"os"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"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"
"os"
) )
func TestListCmd(t *testing.T) { func TestListCmd(t *testing.T) {
@ -46,6 +48,11 @@ func TestListCmd(t *testing.T) {
ch, _ := chartutil.Load(chartPath) ch, _ := chartutil.Load(chartPath)
tests := []releaseCase{ tests := []releaseCase{
{
name: "empty",
rels: []*release.Release{},
expected: "",
},
{ {
name: "with a release", name: "with a release",
rels: []*release.Release{ rels: []*release.Release{
@ -67,6 +74,77 @@ func TestListCmd(t *testing.T) {
}, },
expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tAPP VERSION\tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\t2.X.A \tdefault \n", expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tAPP VERSION\tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\t2.X.A \tdefault \n",
}, },
{
name: "with json output",
flags: []string{"--max", "1", "--output", "json"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide"}),
},
expected: regexp.QuoteMeta(`{"Next":"atlas-guide","Releases":[{"Name":"thomas-guide","Revision":1,"Updated":"`) + `([^"]*)` + regexp.QuoteMeta(`","Status":"DEPLOYED","Chart":"foo-0.1.0-beta.1","AppVersion":"","Namespace":"default"}]}
`),
},
{
name: "with yaml output",
flags: []string{"--max", "1", "--output", "yaml"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide"}),
},
expected: regexp.QuoteMeta(`Next: atlas-guide
Releases:
- AppVersion: ""
Chart: foo-0.1.0-beta.1
Name: thomas-guide
Namespace: default
Revision: 1
Status: DEPLOYED
Updated: `) + `(.*)` + `
`,
},
{
name: "with short json output",
flags: []string{"-q", "--output", "json"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}),
},
expected: regexp.QuoteMeta(`["atlas"]
`),
},
{
name: "with short yaml output",
flags: []string{"-q", "--output", "yaml"},
rels: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}),
},
expected: regexp.QuoteMeta(`- atlas
`),
},
{
name: "with json output without next",
flags: []string{"--output", "json"},
rels: []*release.Release{},
expected: regexp.QuoteMeta(`{"Next":"","Releases":[]}
`),
},
{
name: "with yaml output without next",
flags: []string{"--output", "yaml"},
rels: []*release.Release{},
expected: regexp.QuoteMeta(`Next: ""
Releases: []
`),
},
{
name: "with unknown output format",
flags: []string{"--output", "_unknown_"},
rels: []*release.Release{},
err: true,
expected: regexp.QuoteMeta(``),
},
{ {
name: "list, one deployed, one failed", name: "list, one deployed, one failed",
flags: []string{"-q"}, flags: []string{"-q"},

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@ -22,12 +22,18 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
) )
type pluginError struct {
error
code int
}
// loadPlugins loads plugins into the command list. // loadPlugins loads plugins into the command list.
// //
// This follows a different pattern than the other commands because it has // This follows a different pattern than the other commands because it has
@ -87,7 +93,11 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
if err := prog.Run(); err != nil { if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr) os.Stderr.Write(eerr.Stderr)
return fmt.Errorf("plugin %q exited with error", md.Name) status := eerr.Sys().(syscall.WaitStatus)
return pluginError{
error: fmt.Errorf("plugin %q exited with error", md.Name),
code: status.ExitStatus(),
}
} }
return err return err
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -35,6 +35,7 @@ import (
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/provenance" "k8s.io/helm/pkg/provenance"
"k8s.io/helm/pkg/renderutil"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )
@ -151,7 +152,7 @@ func (p *packageCmd) run() error {
} }
if reqs, err := chartutil.LoadRequirements(ch); err == nil { if reqs, err := chartutil.LoadRequirements(ch); err == nil {
if err := checkDependencies(ch, reqs); err != nil { if err := renderutil.CheckDependencies(ch, reqs); err != nil {
return err return err
} }
} else { } else {
@ -214,7 +215,7 @@ func (p *packageCmd) clearsign(filename string) error {
return err return err
} }
if err := signer.DecryptKey(promptUser); err != nil { if err := signer.DecryptKey(passphraseFetcher); err != nil {
return err return err
} }
@ -228,8 +229,13 @@ func (p *packageCmd) clearsign(filename string) error {
return ioutil.WriteFile(filename+".prov", []byte(sig), 0755) return ioutil.WriteFile(filename+".prov", []byte(sig), 0755)
} }
// promptUser implements provenance.PassphraseFetcher // passphraseFetcher implements provenance.PassphraseFetcher
func promptUser(name string) ([]byte, error) { func passphraseFetcher(name string) ([]byte, error) {
var passphrase = settings.HelmKeyPassphrase()
if passphrase != "" {
return []byte(passphrase), nil
}
fmt.Printf("Password for key %q > ", name) fmt.Printf("Password for key %q > ", name)
pw, err := terminal.ReadPassword(int(syscall.Stdin)) pw, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println() fmt.Println()

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2017 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -64,9 +64,13 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int64Var(&rlsTest.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&rlsTest.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&rlsTest.cleanup, "cleanup", false, "delete test pods upon completion") f.BoolVar(&rlsTest.cleanup, "cleanup", false, "delete test pods upon completion")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -22,9 +22,11 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"syscall"
) )
type repoAddCmd struct { type repoAddCmd struct {
@ -73,6 +75,16 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
} }
func (a *repoAddCmd) run() error { func (a *repoAddCmd) run() error {
if a.username != "" && a.password == "" {
fmt.Fprint(a.out, "Password:")
password, err := readPassword()
fmt.Fprintln(a.out)
if err != nil {
return err
}
a.password = password
}
if err := addRepository(a.name, a.url, a.username, a.password, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil { if err := addRepository(a.name, a.url, a.username, a.password, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil {
return err return err
} }
@ -80,6 +92,14 @@ func (a *repoAddCmd) run() error {
return nil return nil
} }
func readPassword() (string, error) {
password, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", err
}
return string(password), nil
}
func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error { func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
f, err := repo.LoadRepositoriesFile(home.RepositoryFile()) f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
if err != nil { if err != nil {

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -91,7 +91,7 @@ func index(dir, url, mergeTo string) error {
var i2 *repo.IndexFile var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) { if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile() i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0755) i2.WriteFile(mergeTo, 0644)
} else { } else {
i2, err = repo.LoadIndexFile(mergeTo) i2, err = repo.LoadIndexFile(mergeTo)
if err != nil { if err != nil {
@ -101,5 +101,5 @@ func index(dir, url, mergeTo string) error {
i.Merge(i2) i.Merge(i2)
} }
i.SortEntries() i.SortEntries()
return i.WriteFile(out, 0755) return i.WriteFile(out, 0644)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -23,7 +23,8 @@ import (
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/client-go/kubernetes"
"k8s.io/helm/cmd/helm/installer" "k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
@ -44,7 +45,7 @@ type resetCmd struct {
out io.Writer out io.Writer
home helmpath.Home home helmpath.Home
client helm.Interface client helm.Interface
kubeClient internalclientset.Interface kubeClient kubernetes.Interface
} }
func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command { func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
@ -77,16 +78,20 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.)") f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.)")
f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME") f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }
// runReset uninstalls tiller from Kubernetes Cluster and deletes local config // runReset uninstalls tiller from Kubernetes Cluster and deletes local config
func (d *resetCmd) run() error { func (d *resetCmd) run() error {
if d.kubeClient == nil { if d.kubeClient == nil {
c, err := getInternalKubeClient(settings.KubeContext) _, c, err := getKubeClient(settings.KubeContext, settings.KubeConfig)
if err != nil { if err != nil {
return fmt.Errorf("could not get kubernetes client: %s", err) return fmt.Errorf("could not get kubernetes client: %s", err)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -23,8 +23,8 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/kubernetes/pkg/apis/core" "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/client-go/kubernetes/fake"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
@ -101,7 +101,7 @@ func verifyResetCmd(t *testing.T, tc resetCase) {
home: helmpath.Home(home), home: helmpath.Home(home),
client: c, client: c,
kubeClient: fc, kubeClient: fc,
namespace: core.NamespaceDefault, namespace: v1.NamespaceDefault,
} }
err = cmd.run() err = cmd.run()

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -45,6 +45,7 @@ type rollbackCmd struct {
client helm.Interface client helm.Interface
timeout int64 timeout int64
wait bool wait bool
description string
} }
func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command { func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
@ -77,12 +78,17 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
settings.AddFlagsTLS(f)
f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&rollback.force, "force", false, "force resource update through delete/recreate if needed") f.BoolVar(&rollback.force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback")
f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&rollback.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(&rollback.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&rollback.description, "description", "", "specify a description for the release")
// set defaults from environment
settings.InitTLS(f)
return cmd return cmd
} }
@ -96,7 +102,8 @@ func (r *rollbackCmd) run() error {
helm.RollbackDisableHooks(r.disableHooks), helm.RollbackDisableHooks(r.disableHooks),
helm.RollbackVersion(r.revision), helm.RollbackVersion(r.revision),
helm.RollbackTimeout(r.timeout), helm.RollbackTimeout(r.timeout),
helm.RollbackWait(r.wait)) helm.RollbackWait(r.wait),
helm.RollbackDescription(r.description))
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
} }

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -45,6 +45,12 @@ func TestRollbackCmd(t *testing.T) {
flags: []string{"--wait"}, flags: []string{"--wait"},
expected: "Rollback was a success! Happy Helming!", expected: "Rollback was a success! Happy Helming!",
}, },
{
name: "rollback a release with description",
args: []string{"funny-honey", "1"},
flags: []string{"--description", "foo"},
expected: "Rollback was a success! Happy Helming!",
},
{ {
name: "rollback a release without revision", name: "rollback a release without revision",
args: []string{"funny-honey"}, args: []string{"funny-honey"},

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

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

Loading…
Cancel
Save