Merge branch 'master' into 5044-add-app-version-to-history

pull/5069/head
Matt Farina 6 years ago committed by GitHub
commit a55af70ea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,17 +0,0 @@
version: "{build}"
clone_folder: c:\go\src\k8s.io\helm
environment:
GOPATH: c:\go
PATH: c:\ProgramData\bin;$(PATH)
install:
- ps: iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/fishworks/gofish/master/scripts/install.ps1'))
- gofish init
- gofish install glide
- glide install --strip-vendor
cache:
- vendor -> glide.lock
build: "off"
deploy: "off"
test_script:
- go build .\cmd\...
- go test .\...

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

@ -22,6 +22,8 @@ fi
: ${GCLOUD_SERVICE_KEY:?"GCLOUD_SERVICE_KEY environment variable is not set"}
: ${PROJECT_NAME:?"PROJECT_NAME environment variable is not set"}
: ${AZURE_STORAGE_CONNECTION_STRING:?"AZURE_STORAGE_CONNECTION_STRING environment variable is not set"}
: ${AZURE_STORAGE_CONTAINER_NAME:?"AZURE_STORAGE_CONTAINER_NAME environment variable is not set"}
VERSION=
if [[ -n "${CIRCLE_TAG:-}" ]]; then
@ -50,6 +52,14 @@ ${HOME}/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file "${
${HOME}/google-cloud-sdk/bin/gcloud config set project "${PROJECT_NAME}"
docker login -u _json_key -p "$(cat ${HOME}/gcloud-service-key.json)" https://gcr.io
echo "Installing Azure CLI"
apt update
apt install -y apt-transport-https
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ stretch main" | tee /etc/apt/sources.list.d/azure-cli.list
curl -L https://packages.microsoft.com/keys/microsoft.asc | apt-key add
apt update
apt install -y azure-cli
echo "Building the tiller image"
make docker-build VERSION="${VERSION}"
@ -62,3 +72,9 @@ make dist checksum VERSION="${VERSION}"
echo "Pushing binaries to gs bucket"
${HOME}/google-cloud-sdk/bin/gsutil cp ./_dist/* "gs://${PROJECT_NAME}"
echo "Pushing binaries to Azure"
az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING"
echo "Pushing KEYS file to Azure"
az storage blob upload -f "KEYS" -n "KEYS" -c "$AZURE_STORAGE_CONTAINER_NAME" --connection-string "$AZURE_STORAGE_CONNECTION_STRING"

6
.gitignore vendored

@ -11,3 +11,9 @@ rootfs/rudder
vendor/
*.exe
.idea/
*.iml
*.swp
*~
.classpath
.project
.settings/**

@ -1,6 +1,6 @@
# Contributing Guidelines
The Kubernetes Helm project accepts contributions via GitHub pull requests. This document outlines the process to help get your contribution accepted.
The Helm project accepts contributions via GitHub pull requests. This document outlines the process to help get your contribution accepted.
## Reporting a Security Issue
@ -15,7 +15,7 @@ us a chance to try to fix the issue before it is exploited in the wild.
The sign-off is a simple line at the end of the explanation for a commit. All
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/)):
if you can certify the below (from [developercertificate.org](https://developercertificate.org/)):
```
Developer Certificate of Origin
@ -84,12 +84,12 @@ your PR will be rejected by the automated DCO check.
Whether you are a user or contributor, official support channels include:
- GitHub [issues](https://github.com/helm/helm/issues/new)
- Slack [Kubernetes Slack](http://slack.kubernetes.io/):
- User: #helm-users
- Contributor: #helm-dev
- [Issues](https://github.com/helm/helm/issues)
- Slack:
- User: [#helm-users](https://kubernetes.slack.com/messages/C0NH30761/details/)
- Contributor: [#helm-dev](https://kubernetes.slack.com/messages/C51E88VDG/)
Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of.
Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of. It is also worth asking on the Slack channels.
## Milestones
@ -172,41 +172,75 @@ contributing to Helm. All issue types follow the same general lifecycle. Differe
## How to Contribute a Patch
1. Fork the repo, develop and test your code changes.
1. Use sign-off when making each of your commits (see [above](#sign-your-work)).
1. **Fork** the repo [helm](https://github.com/helm/helm)
Go to https://github.com/helm/helm then hit the `Fork` button to fork your own copy of repository **helm** to your github account.
2. **Clone** the forked repo to your local working directory.
```sh
$ git clone https://github.com/$your_github_account/helm.git
```
3. Add an `upstream` remote to keep your fork in sync with the main repo.
```sh
$ cd helm
$ git remote add upstream https://github.com/helm/helm.git
$ git remote -v
origin https://github.com/$your_github_account/helm.git (fetch)
origin https://github.com/$your_github_account/helm.git (push)
upstream https://github.com/helm/helm.git (fetch)
upstream https://github.com/helm/helm.git (push)
```
4. Sync your local `master` branch.
```sh
$ git pull upstream master
```
5. Create a branch to add a new feature or fix issues.
```sh
$ git checkout -b new-feature
```
6. Make any change on the branch `new-feature` then build and test your codes.
7. Include in what will be committed.
```sh
$ git add <file>
```
8. Use sign-off when making each of your commits (see [above](#sign-your-work)).
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.
```sh
$ git commit --signoff
```
9. Submit a pull request.
Coding conventions and standards are explained in the official developer docs:
[Developers Guide](docs/developers.md)
The next section contains more information on the workflow followed for PRs
The next section contains more information on the workflow followed for Pull Requests.
## Pull Requests
Like any good open source project, we use Pull Requests to track code changes
Like any good open source project, we use Pull Requests (PRs) to track code changes.
### PR Lifecycle
1. PR creation
- PRs are usually created to fix or else be a subset of other PRs that fix a particular issue.
- We more than welcome PRs that are currently in progress. They are a great way to keep track of
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
the title.
- It is preferred, but not required, to have a PR tied to a specific issue.
- It is preferred, but not required, to have a PR tied to a specific issue. There can be
circumstances where if it is a quick fix then an issue might be overkill. The details provided
in the PR description would suffice in this case.
2. Triage
- 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.
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.
3. Assigning reviews
- 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.
- Reviews from others in the community, especially those who have encountered a bug or have
requested a feature, are highly encouraged, but not required. Maintainer reviews **are** required
before any merge
- Any PR with the `size/large` label requires 2 review approvals from maintainers before it can be
merged. Those with `size/medium` are per the judgement of the maintainers
merged. Those with `size/medium` or `size/small` are per the judgement of the maintainers.
4. Reviewing/Discussion
- 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
@ -214,17 +248,26 @@ Like any good open source project, we use Pull Requests to track code changes
- 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
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.
- Reviewers should update labels as needed (such as `needs rebase`)
5. Address comments by answering questions or changing code
- A "Changes Requested" review indicates that changes to the code need to be made before they will be
merged.
- Reviewers (maintainers) should update labels as needed (such as `needs rebase`).
- Reviews are also welcome from others in the community, especially those who have encountered a bug or
have requested a feature. In the code review, a message can be added, as well as `LGTM` if the PR is
good to merge. Its also possible to add comments to specific lines in a file, for giving context
to the comment.
5. PR owner should try to be responsive to comments by answering questions or changing code. If the
owner is unsure of any comment, reach out to the person who added the comment in
[#helm-dev](https://kubernetes.slack.com/messages/C51E88VDG/). Once all comments have been addressed,
the PR is ready to be merged.
6. Merge or close
- 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
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
or explicitly request another OWNER do that for them.
- If the owner of a PR is _not_ listed in `OWNERS`, any core committer may
merge the PR once it is approved.
- Before merging a PR, refer to the topic on [Size Labels](#size-labels) below to determine if
the PR requires more than one LGTM to merge.
- 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.
- If the owner of a PR is _not_ listed in `OWNERS`, any maintainer may merge the PR once it is approved.
#### Documentation PRs
@ -235,7 +278,7 @@ Documentation PRs will follow the same lifecycle as other PRs. They will also be
## The Triager
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 stand-up meetings on Thursday. This person will be in charge triaging new PRs and issues
throughout the work week.
## Labels
@ -261,7 +304,7 @@ The following tables define all label types used for Helm. It is split up by cat
| `help wanted` | This issue is one the core maintainers cannot get to right now and would appreciate help with |
| `proposal` | This issue is a proposal |
| `question/support` | This issue is a support request or question |
| `starter` | This issue is a good for someone new to contributing to Helm |
| `good first issue` | This issue is a good for someone new to contributing to Helm |
| `wont fix` | The issue has been discussed and will not be implemented (or accepted in the case of a proposal) |
### PR Specific
@ -279,13 +322,20 @@ The following tables define all label types used for Helm. It is split up by cat
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
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/L`
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/S` even though the number
lines is greater than defined below.
PRs submitted by a core maintainer, regardless of size, only requires approval from one additional
maintainer. This ensures there are at least two maintainers who are aware of any significant PRs
introduced to the codebase.
| Label | Description |
| ----- | ----------- |
| `size/small` | Anything less than or equal to 4 files and 150 lines. Only small amounts of manual testing may be required |
| `size/medium` | Anything greater than `size/small` and less than or equal to 8 files and 300 lines. Manual validation should be required. |
| `size/large` | Anything greater than `size/medium`. This should be thoroughly tested before merging and always requires 2 approvals. This also should be applied to anything that is a significant logic change. |
| `size/XS` | Anything less than or equal to 9 lines ignoring generated files. Only small amounts of manual testing may be required. |
| `size/S` | Anything greater than `size/XS` less than or equal to 29 lines ignoring the generated files. Only small amounts of manual testing may be required. |
| `size/M` | Anything greater than `size/S` less than or equal to 99 lines ignoring the generated files. Manual validation should be required. |
| `size/L` | Anything greater than `size/M` less than or equal to 499 lines ignoring the generated files. This should be thoroughly tested before merging and always requires 2 approvals. This also should be applied to anything that is a significant logic change. |
| `size/XL` | Anything greater than `size/L` less than or equal to 999 lines ignoring the generated files. This should be thoroughly tested before merging and always requires 2 approvals. This also should be applied to anything that is a significant logic change. |
| `size/XXL` | Anything greater than `size/XL`. This should be thoroughly tested before merging and always requires 2 approvals. This also should be applied to anything that is a significant logic change. |

271
KEYS

@ -0,0 +1,271 @@
This file contains the PGP keys of developers who have signed releases of Helm.
For your convenience, commands are provided for those who use pgp and gpg.
For users to import keys:
pgp < KEYS
or
gpg --import KEYS
Developers to add their keys:
pgp -kxa <your name> and append it to this file.
or
(pgpk -ll <your name> && pgpk -xa <your name>) >> KEYS
or
(gpg --list-sigs <your name>
&& gpg --armor --export <your name>) >> KEYS
pub rsa4096/0x461449C25E36B98E 2017-11-10 [SC]
672C657BE06B4B30969C4A57461449C25E36B98E
uid [ultimate] Matthew Farina <matt@mattfarina.com>
sig 3 0x461449C25E36B98E 2017-11-10 Matthew Farina <matt@mattfarina.com>
sig 0x2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein <prydonius@gmail.com>
sig 0x1EF612347F8A9958 2018-12-12 Adam Reese <adam@reese.io>
sig 0x62F49E747D911B60 2018-12-12 Matt Butcher <matt.butcher@microsoft.com>
sub rsa4096/0xCCCE67689DF05738 2017-11-10 [E]
sig 0x461449C25E36B98E 2017-11-10 Matthew Farina <matt@mattfarina.com>
sub rsa4096/0x9436E80BFBA46909 2017-11-10 [S] [expires: 2022-11-09]
sig 0x461449C25E36B98E 2017-11-10 Matthew Farina <matt@mattfarina.com>
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
+YMbPW0/IQ==
=g11H
-----END PGP PUBLIC KEY BLOCK-----
pub rsa4096 2019-05-15 [SC]
F1261BDE929012C8FF2E501D6EA5D7598529A53E
uid [ultimate] Martin Hickey <martin.hickey@ie.ibm.com>
sig 3 6EA5D7598529A53E 2019-05-15 Martin Hickey <martin.hickey@ie.ibm.com>
sub rsa4096 2019-05-15 [E]
sig 6EA5D7598529A53E 2019-05-15 Martin Hickey <martin.hickey@ie.ibm.com>
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
1qneNQ==
=oCPv
-----END PGP PUBLIC KEY BLOCK-----
pub rsa4096 2018-03-14 [SC]
967F8AC5E2216F9F4FD270AD92AA783CBAAE8E3B
uid [ultimate] Matthew Fisher <matt.fisher@microsoft.com>
sig 3 92AA783CBAAE8E3B 2018-03-14 Matthew Fisher <matt.fisher@microsoft.com>
sub rsa4096 2018-03-14 [E]
sig 92AA783CBAAE8E3B 2018-03-14 Matthew Fisher <matt.fisher@microsoft.com>
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
Qknjx8fW
=jz9T
-----END PGP PUBLIC KEY BLOCK-----

@ -1,9 +1,10 @@
DOCKER_REGISTRY ?= gcr.io
IMAGE_PREFIX ?= kubernetes-helm
DEV_IMAGE ?= golang:1.11
DEV_IMAGE ?= golang:1.12.5
SHORT_NAME ?= tiller
SHORT_NAME_RUDDER ?= rudder
TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-386.tar.gz linux-386.tar.gz.sha256 linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-s390x.tar.gz linux-s390x.tar.gz.sha256 windows-amd64.zip windows-amd64.zip.sha256
DIST_DIRS = find * -type d -exec
# go option
@ -44,10 +45,25 @@ dist:
$(DIST_DIRS) zip -r helm-${VERSION}-{}.zip {} \; \
)
.PHONY: fetch-dist
fetch-dist:
mkdir -p _dist
cd _dist && \
for obj in ${TARGET_OBJS} ; do \
curl -sSL -o helm-${VERSION}-$${obj} https://get.helm.sh/helm-${VERSION}-$${obj} ; \
done
.PHONY: sign
sign:
for f in _dist/*.{gz,zip,sha256} ; do \
gpg --armor --detach-sign $${f} ; \
done
.PHONY: checksum
checksum:
for f in _dist/*.{gz,zip} ; do \
shasum -a 256 "$${f}" | awk '{print $$1}' > "$${f}.sha256" ; \
echo -n "Checksum: " && cat $${f}.sha256 ; \
done
.PHONY: check-docker
@ -125,6 +141,10 @@ docker-test-style: check-docker
protoc:
$(MAKE) -C _proto/ all
.PHONY: generate
generate:
$(GO) generate ./...
.PHONY: docs
docs: build
@scripts/update-docs.sh

@ -1,31 +1,20 @@
maintainers:
- adamreese
- bacongobbler
- jascott1
- mattfarina
- michelleN
- nebril
- prydonius
- SlickNik
- technosophos
- thomastaylor312
- viglesiasce
reviewers:
- adamreese
- bacongobbler
- fibonacci1729
- jascott1
- hickeyma
- jdolitsky
- mattfarina
- michelleN
- migmartri
- nebril
- prydonius
- SlickNik
- technosophos
- thomastaylor312
- viglesiasce
emeritus:
- jascott1
- migmartri
- nebril
- seh
- vaikas-google
- rimusz

@ -32,6 +32,7 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install
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!
@ -40,7 +41,10 @@ If you want to use a package manager:
- [Homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`.
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [GoFish](https://gofi.sh/) users can use `gofish install helm`.
- [Snap](https://snapcraft.io/) users can use `sudo snap install helm
--classic`.
To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide).

@ -3,8 +3,8 @@ Protobuf3 type declarations for the Helm API
Packages
- `hapi.chart` Complete serialization of Heml charts
- `hapi.chart` Complete serialization of Helm charts
- `hapi.release` Information about installed charts (Releases) such as metadata about when they were installed, their status, and how they were configured.
- `hapi.services.rudder` Definition for the ReleaseModuleService used by Tiller to manipulate releases on a given node
- `hapi.services.tiller` Definition of the ReleaseService provided by Tiller and used by Helm clients to manipulate releases cluster wide.
- `hapi.version` Version meta-data used by tiller to express it's version
- `hapi.version` Version metadata used by Tiller to express its version

@ -56,4 +56,6 @@ message Hook {
int32 weight = 7;
// DeletePolicies are the policies that indicate when to delete the hook
repeated DeletePolicy delete_policies = 8;
// DeleteTimeout indicates how long to wait for a resource to be deleted before timing out
int64 delete_timeout = 9;
}

@ -29,7 +29,7 @@ message Status {
UNKNOWN = 0;
// Status_DEPLOYED indicates that the release has been pushed to Kubernetes.
DEPLOYED = 1;
// Status_DELETED indicates that a release has been deleted from Kubermetes.
// Status_DELETED indicates that a release has been deleted from Kubernetes.
DELETED = 2;
// Status_SUPERSEDED indicates that this release object is outdated and a newer one exists.
SUPERSEDED = 3;
@ -41,7 +41,7 @@ message Status {
PENDING_INSTALL = 6;
// Status_PENDING_UPGRADE indicates that an upgrade operation is underway.
PENDING_UPGRADE = 7;
// Status_PENDING_ROLLBACK indicates that an rollback operation is underway.
// Status_PENDING_ROLLBACK indicates that a rollback operation is underway.
PENDING_ROLLBACK = 8;
}

@ -92,6 +92,7 @@ message UpgradeReleaseRequest{
bool Wait = 4;
bool Recreate = 5;
bool Force = 6;
bool CleanupOnFail = 7;
}
message UpgradeReleaseResponse{
hapi.release.Release release = 1;
@ -105,6 +106,7 @@ message RollbackReleaseRequest{
bool Wait = 4;
bool Recreate = 5;
bool Force = 6;
bool CleanupOnFail = 7;
}
message RollbackReleaseResponse{
hapi.release.Release release = 1;

@ -76,7 +76,7 @@ service ReleaseService {
rpc RollbackRelease(RollbackReleaseRequest) returns (RollbackReleaseResponse) {
}
// ReleaseHistory retrieves a releasse's history.
// ReleaseHistory retrieves a release's history.
rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) {
}
@ -212,6 +212,10 @@ message UpdateReleaseRequest {
bool force = 11;
// Description, if set, will set the description for the updated release
string description = 12;
// Render subchart notes if enabled
bool subNotes = 13;
// Allow deletion of new resources created in this update when update failed
bool cleanup_on_fail = 14;
}
// UpdateReleaseResponse is the response to an update request.
@ -239,6 +243,8 @@ message RollbackReleaseRequest {
bool force = 8;
// Description, if set, will set the description for the rollback
string description = 9;
// Allow deletion of new resources created in this rollback when rollback failed
bool cleanup_on_fail = 10;
}
// RollbackReleaseResponse is the response to an update request.
@ -281,6 +287,9 @@ message InstallReleaseRequest {
// 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.
@ -298,7 +307,7 @@ message UninstallReleaseRequest {
bool purge = 3;
// timeout specifies the max amount of time any kubernetes client command can run.
int64 timeout = 4;
// Description, if set, will set the description for the uninnstalled release
// Description, if set, will set the description for the uninstalled release
string description = 5;
}

@ -126,13 +126,6 @@ __helm_compgen() {
__helm_compopt() {
true # don't do anything. Not supported by bashcompinit in zsh
}
__helm_declare() {
if [ "$1" == "-F" ]; then
whence -w "$@"
else
builtin declare "$@"
fi
}
__helm_ltrim_colon_completions()
{
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
@ -194,7 +187,7 @@ autoload -U +X bashcompinit && bashcompinit
# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then
LWORD='\<'
RWORD='\>'
fi
@ -210,8 +203,10 @@ __helm_convert_bash_to_zsh() {
-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__helm_ltrim_colon_completions/g" \
-e "s/${LWORD}compgen${RWORD}/__helm_compgen/g" \
-e "s/${LWORD}compopt${RWORD}/__helm_compopt/g" \
-e "s/${LWORD}declare${RWORD}/__helm_declare/g" \
-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
-e "s/\\\$(type${RWORD}/\$(__helm_type/g" \
-e 's/aliashash\["\(.\{1,\}\)"\]/aliashash[\1]/g' \
-e 's/FUNCNAME/funcstack/g' \
<<'BASH_COMPLETION_EOF'
`
out.Write([]byte(zshInitialization))

@ -31,7 +31,8 @@ import (
const createDesc = `
This command creates a chart directory along with the common files and
directories used in a chart.
directories used in a chart. It provides a basic example and is not
meant to cover all Kubernetes resources.
For example, 'helm create foo' will create a directory structure that looks
something like this:
@ -54,6 +55,10 @@ something like this:
do not exist, Helm will attempt to create them as it goes. If the given
destination exists and there are files in that directory, conflicting files
will be overwritten, but other files will be left alone.
The chart that is created by invoking this command contains a Deployment, Ingress
and a Service. To use other Kubernetes resources with your chart, refer to
[The Chart Template Developer's Guide](https://helm.sh/docs/chart_template_guide).
`
type createCmd struct {
@ -68,7 +73,7 @@ func newCreateCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "create NAME",
Short: "create a new chart with the given name",
Short: "Create a new chart with the given name",
Long: createDesc,
RunE: func(cmd *cobra.Command, args []string) error {
cc.home = settings.Home
@ -83,7 +88,7 @@ func newCreateCmd(out io.Writer) *cobra.Command {
},
}
cmd.Flags().StringVarP(&cc.starter, "starter", "p", "", "the named Helm starter scaffold")
cmd.Flags().StringVarP(&cc.starter, "starter", "p", "", "The name or absolute path to Helm starter scaffold")
return cmd
}
@ -101,6 +106,10 @@ func (c *createCmd) run() error {
if c.starter != "" {
// Create from the starter
lstarter := filepath.Join(c.home.Starters(), c.starter)
// If path is absolute, we don't want to prefix it with helm starters folder
if filepath.IsAbs(c.starter) {
lstarter = c.starter
}
return chartutil.CreateFrom(cfile, filepath.Dir(c.name), lstarter)
}

@ -143,7 +143,99 @@ func TestCreateStarterCmd(t *testing.T) {
t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion)
}
expectedTemplateCount := 7
expectedTemplateCount := 8
if l := len(c.Templates); l != expectedTemplateCount {
t.Errorf("Expected %d templates, got %d", expectedTemplateCount, l)
}
found := false
for _, tpl := range c.Templates {
if tpl.Name == "templates/foo.tpl" {
found = true
data := tpl.Data
if string(data) != "test" {
t.Errorf("Expected template 'test', got %q", string(data))
}
}
}
if !found {
t.Error("Did not find foo.tpl")
}
}
func TestCreateStarterAbsoluteCmd(t *testing.T) {
cname := "testchart"
// Make a temp dir
tdir, err := ioutil.TempDir("", "helm-create-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tdir)
thome, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
os.RemoveAll(thome.String())
cleanup()
}()
settings.Home = thome
// Create a starter.
starterchart := filepath.Join(tdir, "starters")
os.Mkdir(starterchart, 0755)
if dest, err := chartutil.Create(&chart.Metadata{Name: "starterchart"}, starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0755); err != nil {
t.Fatalf("Could not write template: %s", err)
}
// CD into it
pwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(tdir); err != nil {
t.Fatal(err)
}
defer os.Chdir(pwd)
// Run a create
cmd := newCreateCmd(ioutil.Discard)
cmd.ParseFlags([]string{"--starter", filepath.Join(starterchart, "starterchart")})
if err := cmd.RunE(cmd, []string{cname}); err != nil {
t.Errorf("Failed to run create: %s", err)
return
}
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
c, err := chartutil.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
if c.Metadata.Name != cname {
t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name)
}
if c.Metadata.ApiVersion != chartutil.ApiVersionV1 {
t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion)
}
expectedTemplateCount := 8
if l := len(c.Templates); l != expectedTemplateCount {
t.Errorf("Expected %d templates, got %d", expectedTemplateCount, l)
}

@ -56,7 +56,7 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command {
Use: "delete [flags] RELEASE_NAME [...]",
Aliases: []string{"del"},
SuggestFor: []string{"remove", "rm"},
Short: "given a release name, delete the release from Kubernetes",
Short: "Given a release name, delete the release from Kubernetes",
Long: deleteDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -79,11 +79,11 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
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.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.StringVar(&del.description, "description", "", "specify a description for the release")
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.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.StringVar(&del.description, "description", "", "Specify a description for the release")
// set defaults from environment
settings.InitTLS(f)

@ -73,7 +73,7 @@ the dependency charts stored locally. The path should start with a prefix of
repository: "file://../dependency_chart/nginx"
If the dependency chart is retrieved locally, it is not required to have the
repository added to helm by "helm add repo". Version matching is also supported
repository added to helm by "helm repo add". Version matching is also supported
for this case.
`
@ -91,7 +91,7 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "dependency update|build|list",
Aliases: []string{"dep", "dependencies"},
Short: "manage a chart's dependencies",
Short: "Manage a chart's dependencies",
Long: dependencyDesc,
}
@ -113,7 +113,7 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "list [flags] CHART",
Aliases: []string{"ls"},
Short: "list the dependencies for the given chart",
Short: "List the dependencies for the given chart",
Long: dependencyListDesc,
RunE: func(cmd *cobra.Command, args []string) error {
cp := "."

@ -29,11 +29,11 @@ const dependencyBuildDesc = `
Build out the charts/ directory from the requirements.lock file.
Build is used to reconstruct a chart's dependencies to the state specified in
the lock file. This will not re-negotiate dependencies, as 'helm dependency update'
does.
the lock file.
If no lock file is found, 'helm dependency build' will mirror the behavior
of 'helm dependency update'.
If no lock file is found, 'helm dependency build' will mirror the behavior of
the 'helm dependency update' command. This means it will update the on-disk
dependencies to mirror the requirements.yaml file and generate a lock file.
`
type dependencyBuildCmd struct {
@ -49,7 +49,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "build [flags] CHART",
Short: "rebuild the charts/ directory based on the requirements.lock file",
Short: "Rebuild the charts/ directory based on the requirements.lock file",
Long: dependencyBuildDesc,
RunE: func(cmd *cobra.Command, args []string) error {
dbc.helmhome = settings.Home
@ -63,8 +63,8 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVar(&dbc.verify, "verify", false, "verify the packages against signatures")
f.StringVar(&dbc.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&dbc.verify, "verify", false, "Verify the packages against signatures")
f.StringVar(&dbc.keyring, "keyring", defaultKeyring(), "Keyring containing public keys")
return cmd
}

@ -57,7 +57,7 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "update [flags] CHART",
Aliases: []string{"up"},
Short: "update charts/ based on the contents of requirements.yaml",
Short: "Update charts/ based on the contents of requirements.yaml",
Long: dependencyUpDesc,
RunE: func(cmd *cobra.Command, args []string) error {
cp := "."
@ -78,9 +78,9 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVar(&duc.verify, "verify", false, "verify the packages against signatures")
f.StringVar(&duc.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&duc.skipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
f.BoolVar(&duc.verify, "verify", false, "Verify the packages against signatures")
f.StringVar(&duc.keyring, "keyring", defaultKeyring(), "Keyring containing public keys")
f.BoolVar(&duc.skipRefresh, "skip-refresh", false, "Do not refresh the local repository cache")
return cmd
}

@ -59,8 +59,8 @@ func newDocsCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&dc.dest, "dir", "./", "directory to which documentation is written")
f.StringVar(&dc.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)")
f.StringVar(&dc.dest, "dir", "./", "Directory to which documentation is written")
f.StringVar(&dc.docTypeString, "type", "markdown", "The type of documentation to generate (markdown, man, bash)")
return cmd
}

@ -73,7 +73,7 @@ func newFetchCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "fetch [flags] [chart URL | repo/chartname] [...]",
Short: "download a chart from a repository and (optionally) unpack it in local directory",
Short: "Download a chart from a repository and (optionally) unpack it in local directory",
Long: fetchDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
@ -96,20 +96,20 @@ func newFetchCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVar(&fch.untar, "untar", false, "if set to true, will untar the chart after downloading it")
f.StringVar(&fch.untardir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
f.BoolVar(&fch.verify, "verify", false, "verify the package against its signature")
f.BoolVar(&fch.verifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
f.StringVar(&fch.version, "version", "", "specific version of a chart. Without this, the latest version is fetched")
f.StringVar(&fch.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.StringVarP(&fch.destdir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
f.StringVar(&fch.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.StringVar(&fch.username, "username", "", "chart repository username")
f.StringVar(&fch.password, "password", "", "chart repository password")
f.BoolVar(&fch.untar, "untar", false, "If set to true, will untar the chart after downloading it")
f.StringVar(&fch.untardir, "untardir", ".", "If untar is specified, this flag specifies the name of the directory into which the chart is expanded")
f.BoolVar(&fch.verify, "verify", false, "Verify the package against its signature")
f.BoolVar(&fch.verifyLater, "prov", false, "Fetch the provenance file, but don't perform verification")
f.StringVar(&fch.version, "version", "", "Specific version of a chart. Without this, the latest version is fetched")
f.StringVar(&fch.keyring, "keyring", defaultKeyring(), "Keyring containing public keys")
f.StringVarP(&fch.destdir, "destination", "d", ".", "Location to write the chart. If this and tardir are specified, tardir is appended to this")
f.StringVar(&fch.repoURL, "repo", "", "Chart repository url where to locate the requested chart")
f.StringVar(&fch.certFile, "cert-file", "", "Identify HTTPS client using this SSL certificate file")
f.StringVar(&fch.keyFile, "key-file", "", "Identify HTTPS client using this SSL key file")
f.StringVar(&fch.caFile, "ca-file", "", "Verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&fch.devel, "devel", false, "Use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.StringVar(&fch.username, "username", "", "Chart repository username")
f.StringVar(&fch.password, "password", "", "Chart repository password")
return cmd
}

@ -41,10 +41,11 @@ chart, the supplied values, and the generated manifest file.
var errReleaseRequired = errors.New("release name is required")
type getCmd struct {
release string
out io.Writer
client helm.Interface
version int32
release string
out io.Writer
client helm.Interface
version int32
template string
}
func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
@ -55,7 +56,7 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "get [flags] RELEASE_NAME",
Short: "download a named release",
Short: "Download a named release",
Long: getHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -72,7 +73,8 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the named release with revision")
f.Int32Var(&get.version, "revision", 0, "Get the named release with revision")
f.StringVar(&get.template, "template", "", "Go template for formatting the output, eg: {{.Release.Name}}")
cmd.AddCommand(newGetValuesCmd(nil, out))
cmd.AddCommand(newGetManifestCmd(nil, out))
@ -91,5 +93,9 @@ func (g *getCmd) run() error {
if err != nil {
return prettyError(err)
}
if g.template != "" {
return tpl(g.template, res, g.out)
}
return printRelease(g.out, res.Release)
}

@ -45,7 +45,7 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "hooks [flags] RELEASE_NAME",
Short: "download all hooks for a named release",
Short: "Download all hooks for a named release",
Long: getHooksHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -59,7 +59,7 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&ghc.version, "revision", 0, "get the named release with revision")
f.Int32Var(&ghc.version, "revision", 0, "Get the named release with revision")
// set defaults from environment
settings.InitTLS(f)

@ -47,7 +47,7 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "manifest [flags] RELEASE_NAME",
Short: "download the manifest for a named release",
Short: "Download the manifest for a named release",
Long: getManifestHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -62,7 +62,7 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the named release with revision")
f.Int32Var(&get.version, "revision", 0, "Get the named release with revision")
// set defaults from environment
settings.InitTLS(f)

@ -44,7 +44,7 @@ func newGetNotesCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "notes [flags] RELEASE_NAME",
Short: "displays the notes of the named release",
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 {
@ -61,7 +61,7 @@ func newGetNotesCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&get.version, "revision", 0, "get the notes of the named release with revision")
f.Int32Var(&get.version, "revision", 0, "Get the notes of the named release with revision")
// set defaults from environment
settings.InitTLS(f)

@ -35,6 +35,13 @@ func TestGetCmd(t *testing.T) {
expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + helm.MockHookTemplate + "\nMANIFEST:",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})},
},
{
name: "get with a formatted release",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "elevated-turkey"}),
args: []string{"elevated-turkey", "--template", "{{.Release.Chart.Metadata.Version}}"},
expected: "0.1.0-beta.1",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "elevated-turkey"})},
},
{
name: "get requires release name arg",
err: true,

@ -47,7 +47,7 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "values [flags] RELEASE_NAME",
Short: "download the values file for a named release",
Short: "Download the values file for a named release",
Long: getValuesHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -62,9 +62,9 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
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)")
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)

@ -29,8 +29,11 @@ import (
func TestGetValuesCmd(t *testing.T) {
releaseWithValues := helm.ReleaseMock(&helm.MockReleaseOptions{
Name: "thomas-guide",
Chart: &chart.Chart{Values: &chart.Config{Raw: `foo2: "bar2"`}},
Name: "thomas-guide",
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "thomas-guide-chart-name"},
Values: &chart.Config{Raw: `foo2: "bar2"`},
},
Config: &chart.Config{Raw: `foo: "bar"`},
})

@ -39,6 +39,104 @@ import (
"k8s.io/helm/pkg/tlsutil"
)
const (
bashCompletionFunc = `
__helm_override_flag_list=(--kubeconfig --kube-context --host --tiller-namespace --home)
__helm_override_flags()
{
local ${__helm_override_flag_list[*]##*-} two_word_of of var
for w in "${words[@]}"; do
if [ -n "${two_word_of}" ]; then
eval "${two_word_of##*-}=\"${two_word_of}=\${w}\""
two_word_of=
continue
fi
for of in "${__helm_override_flag_list[@]}"; do
case "${w}" in
${of}=*)
eval "${of##*-}=\"${w}\""
;;
${of})
two_word_of="${of}"
;;
esac
done
done
for var in "${__helm_override_flag_list[@]##*-}"; do
if eval "test -n \"\$${var}\""; then
eval "echo \${${var}}"
fi
done
}
__helm_binary_name()
{
local helm_binary
helm_binary="${words[0]}"
__helm_debug "${FUNCNAME[0]}: helm_binary is ${helm_binary}"
echo ${helm_binary}
}
__helm_list_releases()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local out filter
# Use ^ to map from the start of the release name
filter="^${words[c]}"
# Use eval in case helm_binary_name or __helm_override_flags contains a variable (e.g., $HOME/bin/h2)
if out=$(eval $(__helm_binary_name) list $(__helm_override_flags) -a -q -m 1000 ${filter} 2>/dev/null); then
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
__helm_list_repos()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local out oflags
oflags=$(__helm_override_flags)
__helm_debug "${FUNCNAME[0]}: __helm_override_flags are ${oflags}"
# Use eval in case helm_binary_name contains a variable (e.g., $HOME/bin/h2)
if out=$(eval $(__helm_binary_name) repo list ${oflags} 2>/dev/null | tail +2 | cut -f1); then
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
__helm_list_plugins()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local out oflags
oflags=$(__helm_override_flags)
__helm_debug "${FUNCNAME[0]}: __helm_override_flags are ${oflags}"
# Use eval in case helm_binary_name contains a variable (e.g., $HOME/bin/h2)
if out=$(eval $(__helm_binary_name) plugin list ${oflags} 2>/dev/null | tail +2 | cut -f1); then
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
__helm_custom_func()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[@] is ${words[@]}"
case ${last_command} in
helm_delete | helm_history | helm_status | helm_test |\
helm_upgrade | helm_rollback | helm_get_*)
__helm_list_releases
return
;;
helm_repo_remove | helm_repo_update)
__helm_list_repos
return
;;
helm_plugin_remove | helm_plugin_update)
__helm_list_plugins
return
;;
*)
;;
esac
}
`
)
var (
tillerTunnel *kube.Tunnel
settings helm_env.EnvSettings
@ -55,24 +153,25 @@ It will also set up any necessary local configuration.
Common actions from this point include:
- helm search: search for charts
- helm fetch: download a chart to your local directory to view
- helm install: upload the chart to Kubernetes
- helm list: list releases of charts
- helm search: Search for charts
- helm fetch: Download a chart to your local directory to view
- helm install: Upload the chart to Kubernetes
- helm list: List releases of charts
Environment:
$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_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
$TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-system")
$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
- $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_NO_PLUGINS: Disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.
- $TILLER_NAMESPACE: Set an alternative Tiller namespace (default "kube-system")
- $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_ENABLE: Enable TLS connection between Helm and Tiller (default "false")
- $HELM_TLS_VERIFY: Enable TLS connection between Helm and Tiller and verify Tiller server certificate (default "false")
- $HELM_TLS_HOSTNAME: The hostname or IP address used to verify the Tiller server certificate (default "127.0.0.1")
- $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
`
@ -102,6 +201,7 @@ func newRootCmd(args []string) *cobra.Command {
PersistentPostRun: func(*cobra.Command, []string) {
teardown()
},
BashCompletionFunction: bashCompletionFunc,
}
flags := cmd.PersistentFlags()
@ -146,7 +246,7 @@ func newRootCmd(args []string) *cobra.Command {
newDocsCmd(out),
// Deprecated
markDeprecated(newRepoUpdateCmd(out), "use 'helm repo update'\n"),
markDeprecated(newRepoUpdateCmd(out), "Use 'helm repo update'\n"),
)
flags.Parse(args)

@ -30,6 +30,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
@ -137,7 +138,7 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error {
}
}
localRepoIndexFile := home.LocalRepository(localRepositoryIndexFile)
localRepoIndexFile := home.LocalRepository(installer.LocalRepositoryIndexFile)
if fi, err := os.Stat(localRepoIndexFile); err != nil {
i := repo.NewIndexFile()
if err := i.WriteFile(localRepoIndexFile, 0644); err != nil {

@ -73,7 +73,7 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "history [flags] RELEASE_NAME",
Long: historyHelp,
Short: "fetch release history",
Short: "Fetch release history",
Aliases: []string{"hist"},
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -90,9 +90,9 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&his.max, "max", 256, "maximum number of revision to include in history")
f.UintVar(&his.colWidth, "col-width", 60, "specifies the max column width of output")
f.StringVarP(&his.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
f.Int32Var(&his.max, "max", 256, "Maximum number of revisions to include in history")
f.UintVar(&his.colWidth, "col-width", 60, "Specifies the max column width of output")
f.StringVarP(&his.outputFormat, "output", "o", "table", "Prints the output in the specified format (json|table|yaml)")
// set defaults from environment
settings.InitTLS(f)

@ -31,7 +31,7 @@ any helm configuration files live.
func newHomeCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "home",
Short: "displays the location of HELM_HOME",
Short: "Displays the location of HELM_HOME",
Long: longHomeHelp,
Run: func(cmd *cobra.Command, args []string) {
h := settings.Home

@ -31,11 +31,10 @@ import (
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/version"
)
const initDesc = `
@ -59,12 +58,6 @@ To dump a manifest containing the Tiller deployment YAML, combine the
'--dry-run' and '--debug' flags.
`
const (
stableRepository = "stable"
localRepository = "local"
localRepositoryIndexFile = "index.yaml"
)
var (
stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com"
// This is the IPv4 loopback, not localhost, because we have to force IPv4
@ -103,7 +96,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "initialize Helm on both client and server",
Short: "Initialize Helm on both client and server",
Long: initDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
@ -118,38 +111,38 @@ func newInitCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image")
f.BoolVar(&i.canary, "canary-image", false, "use the canary Tiller image")
f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed")
f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version")
f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Tiller")
f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote")
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.StringVarP(&i.image, "tiller-image", "i", "", "Override Tiller image")
f.BoolVar(&i.canary, "canary-image", false, "Use the canary Tiller image")
f.BoolVar(&i.upgrade, "upgrade", false, "Upgrade if Tiller is already installed")
f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "Force upgrade of Tiller to the current helm version")
f.BoolVarP(&i.clientOnly, "client-only", "c", false, "If set does not install Tiller")
f.BoolVar(&i.dryRun, "dry-run", false, "Do not install local or remote")
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")
// 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(&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(&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(&tlsServerName, "tiller-tls-hostname", settings.TillerHost, "the server name used to verify the hostname on the returned certificates from Tiller")
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.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(&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(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository")
f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host")
f.StringVar(&i.serviceAccount, "service-account", "", "name of service account")
f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
f.IntVar(&i.replicas, "replicas", 1, "amount of tiller instances to run on the cluster")
f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "Install Tiller with net=host")
f.StringVar(&i.serviceAccount, "service-account", "", "Name of service account")
f.IntVar(&i.maxHistory, "history-max", 0, "Limit the maximum number of revisions saved per release. Use 0 for no limit.")
f.IntVar(&i.replicas, "replicas", 1, "Amount of tiller instances to run on the cluster")
f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)")
f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)")
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")
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.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
}
@ -265,14 +258,8 @@ func (i *initCmd) run() error {
return nil
}
if err := ensureDirectories(i.home, i.out); err != nil {
return err
}
if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil {
return err
}
if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil {
return err
if err := installer.Initialize(i.home, i.out, i.skipRefresh, settings, stableRepositoryURL, localRepositoryURL); err != nil {
return fmt.Errorf("error initializing: %s", err)
}
fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home)
@ -295,8 +282,9 @@ func (i *initCmd) run() error {
if err := i.ping(i.opts.SelectImage()); err != nil {
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 updated to", i.opts.SelectImage(), ".")
} else {
debug("The error received while trying to init: %s", err)
fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+
"(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)")
}
@ -315,7 +303,14 @@ func (i *initCmd) run() error {
fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set")
}
fmt.Fprintln(i.out, "Happy Helming!")
needsDefaultImage := !i.clientOnly && !i.opts.UseCanary && len(i.opts.ImageSpec) == 0 && version.BuildMetadata == "unreleased"
if needsDefaultImage {
fmt.Fprintf(i.out, "\nWarning: You appear to be using an unreleased version of Helm. Please either use the\n"+
"--canary-image flag, or specify your desired tiller version with --tiller-image.\n\n"+
"Ex:\n"+
"$ helm init --tiller-image gcr.io/kubernetes-helm/tiller:v2.8.2\n\n")
}
return nil
}
@ -342,117 +337,6 @@ func (i *initCmd) ping(image string) error {
return nil
}
// ensureDirectories checks to see if $HELM_HOME exists.
//
// If $HELM_HOME does not exist, this function will create it.
func ensureDirectories(home helmpath.Home, out io.Writer) error {
configDirectories := []string{
home.String(),
home.Repository(),
home.Cache(),
home.LocalRepository(),
home.Plugins(),
home.Starters(),
home.Archive(),
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
fmt.Fprintf(out, "Creating %s \n", p)
if err := os.MkdirAll(p, 0755); err != nil {
return fmt.Errorf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
return fmt.Errorf("%s must be a directory", p)
}
}
return nil
}
func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error {
repoFile := home.RepositoryFile()
if fi, err := os.Stat(repoFile); err != nil {
fmt.Fprintf(out, "Creating %s \n", repoFile)
f := repo.NewRepoFile()
sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh, home)
if err != nil {
return err
}
lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out, home)
if err != nil {
return err
}
f.Add(sr)
f.Add(lr)
if err := f.WriteFile(repoFile, 0644); err != nil {
return err
}
} else if fi.IsDir() {
return fmt.Errorf("%s must be a file, not a directory", repoFile)
}
return nil
}
func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL)
c := repo.Entry{
Name: stableRepository,
URL: stableRepositoryURL,
Cache: cacheFile,
}
r, err := repo.NewChartRepository(&c, getter.All(settings))
if err != nil {
return nil, err
}
if skipRefresh {
return &c, nil
}
// In this case, the cacheFile is always absolute. So passing empty string
// is safe.
if err := r.DownloadIndexFile(""); err != nil {
return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error())
}
return &c, nil
}
func initLocalRepo(indexFile, cacheFile string, out io.Writer, home helmpath.Home) (*repo.Entry, error) {
if fi, err := os.Stat(indexFile); err != nil {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL)
i := repo.NewIndexFile()
if err := i.WriteFile(indexFile, 0644); err != nil {
return nil, err
}
//TODO: take this out and replace with helm update functionality
if err := createLink(indexFile, cacheFile, home); err != nil {
return nil, err
}
} else if fi.IsDir() {
return nil, fmt.Errorf("%s must be a file, not a directory", indexFile)
}
return &repo.Entry{
Name: localRepository,
URL: localRepositoryURL,
Cache: cacheFile,
}, nil
}
func ensureRepoFileFormat(file string, out io.Writer) error {
r, err := repo.LoadRepositoriesFile(file)
if err == repo.ErrRepoOutOfDate {
fmt.Fprintln(out, "Updating repository file format...")
if err := r.WriteFile(file, 0644); err != nil {
return err
}
}
return nil
}
// watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we
// want to wait before we call New().
//

@ -28,8 +28,8 @@ import (
"github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -83,7 +83,7 @@ func TestInitCmd_exists(t *testing.T) {
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset(&v1beta1.Deployment{
fc := fake.NewSimpleClientset(&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: v1.NamespaceDefault,
Name: "tiller-deploy",
@ -179,51 +179,6 @@ func TestInitCmd_dryRun(t *testing.T) {
}
}
func TestEnsureHome(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(home)
b := bytes.NewBuffer(nil)
hh := helmpath.Home(home)
settings.Home = hh
if err := ensureDirectories(hh, b); err != nil {
t.Error(err)
}
if err := ensureDefaultRepos(hh, b, false); err != nil {
t.Error(err)
}
if err := ensureDefaultRepos(hh, b, true); err != nil {
t.Error(err)
}
if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil {
t.Error(err)
}
expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache(), hh.LocalRepository()}
for _, dir := range expectedDirs {
if fi, err := os.Stat(dir); err != nil {
t.Errorf("%s", err)
} else if !fi.IsDir() {
t.Errorf("%s is not a directory", fi)
}
}
if fi, err := os.Stat(hh.RepositoryFile()); err != nil {
t.Error(err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
}
if fi, err := os.Stat(hh.LocalRepository(localRepositoryIndexFile)); err != nil {
t.Errorf("%s", err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
}
}
func TestInitCmd_tlsOptions(t *testing.T) {
const testDir = "../../testdata"

@ -27,27 +27,26 @@ import (
"k8s.io/helm/pkg/chartutil"
)
const inspectDesc = `
const (
inspectDesc = `
This command inspects a chart and displays information. It takes a chart reference
('stable/drupal'), a full path to a directory or packaged chart, or a URL.
Inspect prints the contents of the Chart.yaml file and the values.yaml file.
`
const inspectValuesDesc = `
inspectValuesDesc = `
This command inspects a chart (directory, file, or URL) and displays the contents
of the values.yaml file
`
const inspectChartDesc = `
inspectChartDesc = `
This command inspects a chart (directory, file, or URL) and displays the contents
of the Charts.yaml file
`
const readmeChartDesc = `
readmeChartDesc = `
This command inspects a chart (directory, file, or URL) and displays the contents
of the README file
`
)
type inspectCmd struct {
chartpath string
@ -59,6 +58,7 @@ type inspectCmd struct {
repoURL string
username string
password string
devel bool
certFile string
keyFile string
@ -82,18 +82,15 @@ func newInspectCmd(out io.Writer) *cobra.Command {
inspectCommand := &cobra.Command{
Use: "inspect [CHART]",
Short: "inspect a chart",
Short: "Inspect a chart",
Long: inspectDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
if err := insp.prepare(args[0]); err != nil {
return err
}
insp.chartpath = cp
return insp.run()
},
}
@ -107,12 +104,9 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
if err := insp.prepare(args[0]); err != nil {
return err
}
insp.chartpath = cp
return insp.run()
},
}
@ -126,12 +120,9 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
if err := insp.prepare(args[0]); err != nil {
return err
}
insp.chartpath = cp
return insp.run()
},
}
@ -145,68 +136,71 @@ func newInspectCmd(out io.Writer) *cobra.Command {
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring,
insp.certFile, insp.keyFile, insp.caFile)
if err != nil {
if err := insp.prepare(args[0]); err != nil {
return err
}
insp.chartpath = cp
return insp.run()
},
}
cmds := []*cobra.Command{inspectCommand, readmeSubCmd, valuesSubCmd, chartSubCmd}
vflag := "verify"
vdesc := "verify the provenance data for this chart"
vdesc := "Verify the provenance data for this chart"
for _, subCmd := range cmds {
subCmd.Flags().BoolVar(&insp.verify, vflag, false, vdesc)
}
kflag := "keyring"
kdesc := "path to the keyring containing public verification keys"
kdesc := "Path to the keyring containing public verification keys"
kdefault := defaultKeyring()
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc)
}
verflag := "version"
verdesc := "version of the chart. By default, the newest chart is shown"
verdesc := "Version of the chart. By default, the newest chart is shown"
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.version, verflag, "", verdesc)
}
repoURL := "repo"
repoURLdesc := "chart repository url where to locate the requested chart"
repoURLdesc := "Chart repository url where to locate the requested chart"
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc)
}
username := "username"
usernamedesc := "chart repository username where to locate the requested chart"
usernamedesc := "Chart repository username where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.username, username, "", usernamedesc)
valuesSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc)
chartSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc)
password := "password"
passworddesc := "chart repository password where to locate the requested chart"
passworddesc := "Chart repository password where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.password, password, "", passworddesc)
valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
develFlag := "devel"
develDesc := "Use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored."
for _, subCmd := range cmds {
subCmd.Flags().BoolVar(&insp.devel, develFlag, false, develDesc)
}
certFile := "cert-file"
certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle"
certFiledesc := "Verify certificates of HTTPS-enabled servers using this CA bundle"
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.certFile, certFile, "", certFiledesc)
}
keyFile := "key-file"
keyFiledesc := "identify HTTPS client using this SSL key file"
keyFiledesc := "Identify HTTPS client using this SSL key file"
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.keyFile, keyFile, "", keyFiledesc)
}
caFile := "ca-file"
caFiledesc := "chart repository url where to locate the requested chart"
caFiledesc := "Chart repository url where to locate the requested chart"
for _, subCmd := range cmds {
subCmd.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc)
}
@ -218,6 +212,22 @@ func newInspectCmd(out io.Writer) *cobra.Command {
return inspectCommand
}
func (i *inspectCmd) prepare(chart string) error {
debug("Original chart version: %q", i.version)
if i.version == "" && i.devel {
debug("setting version to >0.0.0-0")
i.version = ">0.0.0-0"
}
cp, err := locateChartPath(i.repoURL, i.username, i.password, chart, i.version, i.verify, i.keyring,
i.certFile, i.keyFile, i.caFile)
if err != nil {
return err
}
i.chartpath = cp
return nil
}
func (i *inspectCmd) run() error {
chrt, err := chartutil.Load(i.chartpath)
if err != nil {

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

@ -131,16 +131,19 @@ type installCmd struct {
version string
timeout int64
wait bool
atomic bool
repoURL string
username string
password string
devel bool
depUp bool
subNotes bool
description string
certFile string
keyFile string
caFile string
output string
}
type valueFiles []string
@ -168,7 +171,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "install [CHART]",
Short: "install a chart archive",
Short: "Install a chart archive",
Long: installDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -189,37 +192,42 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
inst.chartPath = cp
inst.client = ensureHelmClient(inst.client)
inst.wait = inst.wait || inst.atomic
return inst.run()
},
}
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.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.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&inst.disableCRDHook, "no-crd-hook", false, "prevent CRD hooks from running, but run other hooks")
f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.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.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.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
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.StringVar(&inst.description, "description", "", "specify a description for the release")
f.VarP(&inst.valueFiles, "values", "f", "Specify values in a YAML file or a URL(can specify multiple)")
f.StringVarP(&inst.name, "name", "n", "", "The 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.BoolVar(&inst.dryRun, "dry-run", false, "Simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "Prevent hooks from running during install")
f.BoolVar(&inst.disableCRDHook, "no-crd-hook", false, "Prevent CRD hooks from running, but run other hooks")
f.BoolVar(&inst.replace, "replace", false, "Re-use the given name, even if that name is already used. This is unsafe in production")
f.StringArrayVar(&inst.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.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.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.version, "version", "", "Specify the exact chart version to install. If this is not specified, the latest version is installed")
f.Int64Var(&inst.timeout, "timeout", 300, "Time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&inst.wait, "wait", false, "If set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&inst.atomic, "atomic", false, "If set, installation process purges chart on fail, also sets --wait flag")
f.StringVar(&inst.repoURL, "repo", "", "Chart repository url where to locate the requested chart")
f.StringVar(&inst.username, "username", "", "Chart repository username where to locate the requested chart")
f.StringVar(&inst.password, "password", "", "Chart repository password where to locate the requested chart")
f.StringVar(&inst.certFile, "cert-file", "", "Identify HTTPS client using this SSL certificate file")
f.StringVar(&inst.keyFile, "key-file", "", "Identify HTTPS client using this SSL key file")
f.StringVar(&inst.caFile, "ca-file", "", "Verify certificates of HTTPS-enabled servers using this CA bundle")
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.subNotes, "render-subchart-notes", false, "Render subchart notes along with the parent")
f.StringVar(&inst.description, "description", "", "Specify a description for the release")
bindOutputFlag(cmd, &inst.output)
// set defaults from environment
settings.InitTLS(f)
@ -249,8 +257,8 @@ func (i *installCmd) run() error {
fmt.Printf("FINAL NAME: %s\n", i.name)
}
if msgs := validation.IsDNS1123Label(i.name); i.name != "" && len(msgs) > 0 {
return fmt.Errorf("release name %s is not a valid DNS label: %s", i.name, strings.Join(msgs, ";"))
if msgs := validation.IsDNS1123Subdomain(i.name); i.name != "" && len(msgs) > 0 {
return fmt.Errorf("release name %s is invalid: %s", i.name, strings.Join(msgs, ";"))
}
// Check chart requirements to make sure all dependencies are present in /charts
@ -300,10 +308,28 @@ func (i *installCmd) run() error {
helm.InstallReuseName(i.replace),
helm.InstallDisableHooks(i.disableHooks),
helm.InstallDisableCRDHook(i.disableCRDHook),
helm.InstallSubNotes(i.subNotes),
helm.InstallTimeout(i.timeout),
helm.InstallWait(i.wait),
helm.InstallDescription(i.description))
if err != nil {
if i.atomic {
fmt.Fprintf(os.Stdout, "INSTALL FAILED\nPURGING CHART\nError: %v\n", prettyError(err))
deleteSideEffects := &deleteCmd{
name: i.name,
disableHooks: i.disableHooks,
purge: true,
timeout: i.timeout,
description: "",
dryRun: i.dryRun,
out: i.out,
client: i.client,
}
if err := deleteSideEffects.run(); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Successfully purged a chart!\n")
}
return prettyError(err)
}
@ -311,7 +337,10 @@ func (i *installCmd) run() error {
if rel == nil {
return nil
}
i.printRelease(rel)
if outputFormat(i.output) == outputTable {
i.printRelease(rel)
}
// If this is a dry run, we can't display status.
if i.dryRun {
@ -327,8 +356,8 @@ func (i *installCmd) run() error {
if err != nil {
return prettyError(err)
}
PrintStatus(i.out, status)
return nil
return write(i.out, &statusWriter{status}, outputFormat(i.output))
}
// Merges source and destination map, preferring values from the source map

@ -113,6 +113,14 @@ func TestInstall(t *testing.T) {
expected: "apollo",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}),
},
// Install, with atomic
{
name: "install with a atomic",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name apollo", " "),
expected: "apollo",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}),
},
// Install, using the name-template
{
name: "install with name-template",
@ -169,7 +177,6 @@ func TestInstall(t *testing.T) {
name: "install chart with release name using periods",
args: []string{"testdata/testcharts/alpine"},
flags: []string{"--name", "foo.bar"},
err: true,
},
{
name: "install chart with release name using underscores",
@ -184,6 +191,22 @@ func TestInstall(t *testing.T) {
flags: []string{"--name-template", "{{UPPER \"foobar\"}}"},
err: true,
},
// Install, using --output json
{
name: "install using output json",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name virgil --output json", " "),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}),
expected: regexp.QuoteMeta(`{"name":"virgil","info":{"status":{"code":1},"first_deployed":{"seconds":242085845},"last_deployed":{"seconds":242085845},"Description":"Release mock"},"namespace":"default"}`),
},
// Install, using --output yaml
{
name: "install using output yaml",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name virgil --output yaml", " "),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}),
expected: "info:\n Description: Release mock\n first_deployed:\n seconds: 242085845\n last_deployed:\n seconds: 242085845\n status:\n code: 1\nname: virgil\nnamespace: default\n",
},
}
runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command {

@ -0,0 +1,163 @@
/*
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 installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"io"
"os"
"k8s.io/helm/pkg/getter"
helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo"
)
const (
stableRepository = "stable"
// LocalRepository is the standard name of the local repository
LocalRepository = "local"
// LocalRepositoryIndexFile is the standard name of the local repository index file
LocalRepositoryIndexFile = "index.yaml"
)
// Initialize initializes local config
//
// Returns an error if the command failed.
func Initialize(home helmpath.Home, out io.Writer, skipRefresh bool, settings helm_env.EnvSettings, stableRepositoryURL, localRepositoryURL string) error {
if err := ensureDirectories(home, out); err != nil {
return err
}
if err := ensureDefaultRepos(home, out, skipRefresh, settings, stableRepositoryURL, localRepositoryURL); err != nil {
return err
}
return ensureRepoFileFormat(home.RepositoryFile(), out)
}
// ensureDirectories checks to see if $HELM_HOME exists.
//
// If $HELM_HOME does not exist, this function will create it.
func ensureDirectories(home helmpath.Home, out io.Writer) error {
configDirectories := []string{
home.String(),
home.Repository(),
home.Cache(),
home.LocalRepository(),
home.Plugins(),
home.Starters(),
home.Archive(),
}
for _, p := range configDirectories {
if fi, err := os.Stat(p); err != nil {
fmt.Fprintf(out, "Creating %s \n", p)
if err := os.MkdirAll(p, 0755); err != nil {
return fmt.Errorf("Could not create %s: %s", p, err)
}
} else if !fi.IsDir() {
return fmt.Errorf("%s must be a directory", p)
}
}
return nil
}
func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool, settings helm_env.EnvSettings, stableRepositoryURL, localRepositoryURL string) error {
repoFile := home.RepositoryFile()
if fi, err := os.Stat(repoFile); err != nil {
fmt.Fprintf(out, "Creating %s \n", repoFile)
f := repo.NewRepoFile()
sr, err := initStableRepo(home.CacheIndex(stableRepository), home, out, skipRefresh, settings, stableRepositoryURL)
if err != nil {
return err
}
lr, err := initLocalRepo(home.LocalRepository(LocalRepositoryIndexFile), home.CacheIndex("local"), home, out, settings, localRepositoryURL)
if err != nil {
return err
}
f.Add(sr)
f.Add(lr)
if err := f.WriteFile(repoFile, 0644); err != nil {
return err
}
} else if fi.IsDir() {
return fmt.Errorf("%s must be a file, not a directory", repoFile)
}
return nil
}
func initStableRepo(cacheFile string, home helmpath.Home, out io.Writer, skipRefresh bool, settings helm_env.EnvSettings, stableRepositoryURL string) (*repo.Entry, error) {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL)
c := repo.Entry{
Name: stableRepository,
URL: stableRepositoryURL,
Cache: cacheFile,
}
r, err := repo.NewChartRepository(&c, getter.All(settings))
if err != nil {
return nil, err
}
if skipRefresh {
return &c, nil
}
// In this case, the cacheFile is always absolute. So passing empty string
// is safe.
if err := r.DownloadIndexFile(""); err != nil {
return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error())
}
return &c, nil
}
func initLocalRepo(indexFile, cacheFile string, home helmpath.Home, out io.Writer, settings helm_env.EnvSettings, localRepositoryURL string) (*repo.Entry, error) {
if fi, err := os.Stat(indexFile); err != nil {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", LocalRepository, localRepositoryURL)
i := repo.NewIndexFile()
if err := i.WriteFile(indexFile, 0644); err != nil {
return nil, err
}
//TODO: take this out and replace with helm update functionality
if err := createLink(indexFile, cacheFile, home); err != nil {
return nil, err
}
} else if fi.IsDir() {
return nil, fmt.Errorf("%s must be a file, not a directory", indexFile)
}
return &repo.Entry{
Name: LocalRepository,
URL: localRepositoryURL,
Cache: cacheFile,
}, nil
}
func ensureRepoFileFormat(file string, out io.Writer) error {
r, err := repo.LoadRepositoriesFile(file)
if err == repo.ErrRepoOutOfDate {
fmt.Fprintln(out, "Updating repository file format...")
if err := r.WriteFile(file, 0644); err != nil {
return err
}
}
return nil
}

@ -0,0 +1,120 @@
/*
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 installer // import "k8s.io/helm/cmd/helm/installer"
import (
"bytes"
"io/ioutil"
"os"
"testing"
helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
)
func TestInitialize(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(home)
b := bytes.NewBuffer(nil)
hh := helmpath.Home(home)
settings := helm_env.EnvSettings{
Home: hh,
}
stableRepositoryURL := "https://kubernetes-charts.storage.googleapis.com"
localRepositoryURL := "http://127.0.0.1:8879/charts"
if err := Initialize(hh, b, false, settings, stableRepositoryURL, localRepositoryURL); err != nil {
t.Error(err)
}
expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache(), hh.LocalRepository()}
for _, dir := range expectedDirs {
if fi, err := os.Stat(dir); err != nil {
t.Errorf("%s", err)
} else if !fi.IsDir() {
t.Errorf("%s is not a directory", fi)
}
}
if fi, err := os.Stat(hh.RepositoryFile()); err != nil {
t.Error(err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
}
if fi, err := os.Stat(hh.LocalRepository(LocalRepositoryIndexFile)); err != nil {
t.Errorf("%s", err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
}
}
func TestEnsureHome(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(home)
b := bytes.NewBuffer(nil)
hh := helmpath.Home(home)
settings := helm_env.EnvSettings{
Home: hh,
}
stableRepositoryURL := "https://kubernetes-charts.storage.googleapis.com"
localRepositoryURL := "http://127.0.0.1:8879/charts"
if err := ensureDirectories(hh, b); err != nil {
t.Error(err)
}
if err := ensureDefaultRepos(hh, b, false, settings, stableRepositoryURL, localRepositoryURL); err != nil {
t.Error(err)
}
if err := ensureDefaultRepos(hh, b, true, settings, stableRepositoryURL, localRepositoryURL); err != nil {
t.Error(err)
}
if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil {
t.Error(err)
}
expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache(), hh.LocalRepository()}
for _, dir := range expectedDirs {
if fi, err := os.Stat(dir); err != nil {
t.Errorf("%s", err)
} else if !fi.IsDir() {
t.Errorf("%s is not a directory", fi)
}
}
if fi, err := os.Stat(hh.RepositoryFile()); err != nil {
t.Error(err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
}
if fi, err := os.Stat(hh.LocalRepository(LocalRepositoryIndexFile)); err != nil {
t.Errorf("%s", err)
} else if fi.IsDir() {
t.Errorf("%s should not be a directory", fi)
}
}

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"os"

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"os"

@ -17,32 +17,32 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
"k8s.io/helm/pkg/version"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/tiller/environment"
)
// Install uses Kubernetes client to install Tiller.
//
// Returns an error if the command failed.
func Install(client kubernetes.Interface, opts *Options) error {
if err := createDeployment(client.ExtensionsV1beta1(), opts); err != nil {
if err := createDeployment(client.AppsV1(), opts); err != nil {
return err
}
if err := createService(client.CoreV1(), opts.Namespace); err != nil {
@ -60,51 +60,108 @@ func Install(client kubernetes.Interface, opts *Options) error {
//
// Returns an error if the command failed.
func Upgrade(client kubernetes.Interface, opts *Options) error {
obj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
if err != nil {
appsobj, err := client.AppsV1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
if err == nil {
// Can happen in two cases:
// 1. helm init inserted an apps/v1 Deployment up front in Kubernetes
// 2. helm init inserted an extensions/v1beta1 Deployment against a K8s cluster already
// supporting apps/v1 Deployment. In such a case K8s is returning the apps/v1 object anyway.`
// (for the same reason "kubectl convert" is being deprecated)
return upgradeAppsTillerDeployment(client, opts, appsobj)
}
extensionsobj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
if err == nil {
// User performed helm init against older version of kubernetes (Previous to 1.9)
return upgradeExtensionsTillerDeployment(client, opts, extensionsobj)
}
return err
}
func upgradeAppsTillerDeployment(client kubernetes.Interface, opts *Options, obj *appsv1.Deployment) error {
// Update the PodTemplateSpec section of the deployment
if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil {
return err
}
tillerImage := obj.Spec.Template.Spec.Containers[0].Image
if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
if _, err := client.AppsV1().Deployments(opts.Namespace).Update(obj); err != nil {
return err
}
obj.Spec.Template.Spec.Containers[0].Image = opts.SelectImage()
obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
// If the service does not exist that would mean we are upgrading from a Tiller version
// that didn't deploy the service, so install it.
_, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return createService(client.CoreV1(), opts.Namespace)
}
return err
}
func upgradeExtensionsTillerDeployment(client kubernetes.Interface, opts *Options, obj *extensionsv1beta1.Deployment) error {
// Update the PodTemplateSpec section of the deployment
if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil {
return err
}
if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
return err
}
// If the service does not exist that would mean we are upgrading from a Tiller version
// that didn't deploy the service, so install it.
_, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
_, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return createService(client.CoreV1(), opts.Namespace)
}
return err
}
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
func semverCompare(image string) int {
split := strings.Split(image, ":")
if len(split) < 2 {
// If we don't know the version, we consider the client version newer.
return 1
func updatePodTemplate(podSpec *v1.PodSpec, opts *Options) error {
tillerImage := podSpec.Containers[0].Image
clientImage := opts.SelectImage()
if semverCompare(tillerImage, clientImage) == -1 && !opts.ForceUpgrade {
return fmt.Errorf("current Tiller version %s is newer than client version %s, use --force-upgrade to downgrade", tillerImage, clientImage)
}
tillerVersion, err := semver.NewVersion(split[1])
podSpec.Containers[0].Image = clientImage
podSpec.Containers[0].ImagePullPolicy = opts.pullPolicy()
podSpec.ServiceAccountName = opts.ServiceAccount
return nil
}
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
func semverCompare(tillerImage, clientImage string) int {
tillerVersion, err := string2semver(tillerImage)
if err != nil {
// same thing with unparsable tiller versions (e.g. canary releases).
return 1
}
clientVersion, err := semver.NewVersion(version.Version)
// clientVersion, err := semver.NewVersion(currentVersion)
clientVersion, err := string2semver(clientImage)
if err != nil {
// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
return 1
}
return clientVersion.Compare(tillerVersion)
}
func string2semver(image string) (*semver.Version, error) {
split := strings.Split(image, ":")
if len(split) < 2 {
// If we don't know the version, we consider the client version newer.
return nil, fmt.Errorf("no repository in image %s", image)
}
return semver.NewVersion(split[1])
}
// createDeployment creates the Tiller Deployment resource.
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
func createDeployment(client appsv1client.DeploymentsGetter, opts *Options) error {
obj, err := generateDeployment(opts)
if err != nil {
return err
@ -117,14 +174,14 @@ func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options)
// Deployment gets a deployment object that can be used to generate a manifest
// as a string. This object should not be submitted directly to the Kubernetes
// api
func Deployment(opts *Options) (*v1beta1.Deployment, error) {
func Deployment(opts *Options) (*appsv1.Deployment, error) {
dep, err := generateDeployment(opts)
if err != nil {
return nil, err
}
dep.TypeMeta = metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "extensions/v1beta1",
APIVersion: "apps/v1",
}
return dep, nil
}
@ -183,7 +240,7 @@ func generateLabels(labels map[string]string) map[string]string {
return labels
}
// parseNodeSelectors parses a comma delimited list of key=values pairs into a map.
// parseNodeSelectorsInto parses a comma delimited list of key=values pairs into a map.
func parseNodeSelectorsInto(labels string, m map[string]string) error {
kv := strings.Split(labels, ",")
for _, v := range kv {
@ -196,7 +253,7 @@ func parseNodeSelectorsInto(labels string, m map[string]string) error {
}
return nil
}
func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
func generateDeployment(opts *Options) (*appsv1.Deployment, error) {
labels := generateLabels(map[string]string{"name": "tiller"})
nodeSelectors := map[string]string{}
if len(opts.NodeSelectors) > 0 {
@ -205,14 +262,17 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
return nil, err
}
}
d := &v1beta1.Deployment{
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: opts.Namespace,
Name: deploymentName,
Labels: labels,
},
Spec: v1beta1.DeploymentSpec{
Spec: appsv1.DeploymentSpec{
Replicas: opts.getReplicas(),
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
@ -226,8 +286,8 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
Image: opts.SelectImage(),
ImagePullPolicy: opts.pullPolicy(),
Ports: []v1.ContainerPort{
{ContainerPort: 44134, Name: "tiller"},
{ContainerPort: 44135, Name: "http"},
{ContainerPort: environment.DefaultTillerPort, Name: "tiller"},
{ContainerPort: environment.DefaultTillerProbePort, Name: "http"},
},
Env: []v1.EnvVar{
{Name: "TILLER_NAMESPACE", Value: opts.Namespace},
@ -237,7 +297,7 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
Path: "/liveness",
Port: intstr.FromInt(44135),
Port: intstr.FromInt(environment.DefaultTillerProbePort),
},
},
InitialDelaySeconds: 1,
@ -247,7 +307,7 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
Path: "/readiness",
Port: intstr.FromInt(44135),
Port: intstr.FromInt(environment.DefaultTillerProbePort),
},
},
InitialDelaySeconds: 1,
@ -296,7 +356,7 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
// merge them and convert back to Deployment
if len(opts.Values) > 0 {
// base deployment struct
var dd v1beta1.Deployment
var dd appsv1.Deployment
// get YAML from original deployment
dy, err := yaml.Marshal(d)
if err != nil {
@ -341,7 +401,7 @@ func generateService(namespace string) *v1.Service {
Ports: []v1.ServicePort{
{
Name: "tiller",
Port: 44134,
Port: environment.DefaultTillerPort,
TargetPort: intstr.FromString("tiller"),
},
},

@ -17,14 +17,15 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/ghodss/yaml"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
@ -53,6 +54,10 @@ func TestDeployment(t *testing.T) {
t.Fatalf("%s: error %q", tt.name, err)
}
// Unreleased versions of helm don't have a release image. See issue 3370
if tt.name == "default" && version.BuildMetadata == "unreleased" {
tt.expect = "gcr.io/kubernetes-helm/tiller:canary"
}
if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect {
t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got)
}
@ -187,7 +192,7 @@ func TestInstall(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.CreateAction).GetObject().(*appsv1.Deployment)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
@ -234,7 +239,7 @@ func TestInstallHA(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.CreateAction).GetObject().(*appsv1.Deployment)
replicas := obj.Spec.Replicas
if int(*replicas) != 2 {
t.Errorf("expected replicas = 2, got '%d'", replicas)
@ -258,7 +263,7 @@ func TestInstall_WithTLS(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.CreateAction).GetObject().(*appsv1.Deployment)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
@ -326,7 +331,7 @@ func TestInstall_WithTLS(t *testing.T) {
func TestInstall_canary(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.CreateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != "gcr.io/kubernetes-helm/tiller:canary" {
t.Errorf("expected canary image, got '%s'", i)
@ -364,7 +369,7 @@ func TestUpgrade(t *testing.T) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.UpdateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
@ -403,7 +408,7 @@ func TestUpgrade_serviceNotFound(t *testing.T) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.UpdateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
@ -448,7 +453,7 @@ func TestUgrade_newerVersion(t *testing.T) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.UpdateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
@ -508,7 +513,7 @@ func TestUpgrade_identical(t *testing.T) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.UpdateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
@ -549,7 +554,7 @@ func TestUpgrade_canaryClient(t *testing.T) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.UpdateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
@ -590,7 +595,7 @@ func TestUpgrade_canaryServer(t *testing.T) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
obj := action.(testcore.UpdateAction).GetObject().(*appsv1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
@ -712,9 +717,32 @@ func TestDeployment_WithSetValues(t *testing.T) {
// convert our expected value to match the result type for comparison
ev := tt.expect
intType := reflect.TypeOf(int64(0))
floatType := reflect.TypeOf(float64(0))
switch pvt := pv.(type) {
case json.Number:
evv := reflect.ValueOf(ev)
evv = reflect.Indirect(evv)
switch ev.(type) {
case float32, float64:
evv = evv.Convert(floatType)
if fpv, err := pv.(json.Number).Float64(); err != nil {
t.Errorf("Failed to convert json number to float: %s", err)
} else if fpv != evv.Float() {
t.Errorf("%s: expected float value %q, got %f", tt.name, tt.expect, fpv)
}
case byte, int, int32, int64:
evv = evv.Convert(intType)
if ipv, err := pv.(json.Number).Int64(); err != nil {
t.Errorf("Failed to convert json number to int: %s", err)
} else if ipv != evv.Int() {
t.Errorf("%s: expected int value %q, got %d", tt.name, tt.expect, ipv)
}
default:
t.Errorf("Unknown primitive type: %s", reflect.TypeOf(ev))
}
case float64:
floatType := reflect.TypeOf(float64(0))
v := reflect.ValueOf(ev)
v = reflect.Indirect(v)
if !v.Type().ConvertibleTo(floatType) {

@ -24,7 +24,12 @@ import (
"k8s.io/helm/pkg/version"
)
const defaultImage = "gcr.io/kubernetes-helm/tiller"
const (
defaultImage = "gcr.io/kubernetes-helm/tiller"
fmtJSON OutputFormat = "json"
fmtYAML OutputFormat = "yaml"
)
// Options control how to install Tiller into a cluster, upgrade, and uninstall Tiller from a cluster.
type Options struct {
@ -50,7 +55,7 @@ type Options struct {
// 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
// ForceUpgrade allows to force upgrading tiller if deployed version is greater than current version
ForceUpgrade bool
// ImageSpec identifies the image Tiller will use when deployed.
@ -105,6 +110,9 @@ func (opts *Options) SelectImage() string {
case opts.UseCanary:
return defaultImage + ":canary"
case opts.ImageSpec == "":
if version.BuildMetadata == "unreleased" {
return defaultImage + ":canary"
}
return fmt.Sprintf("%s:%s", defaultImage, version.Version)
default:
return opts.ImageSpec
@ -151,11 +159,6 @@ func (f *OutputFormat) Type() string {
return "OutputFormat"
}
const (
fmtJSON OutputFormat = "json"
fmtYAML OutputFormat = "yaml"
)
// Set validates and sets the value of the OutputFormat
func (f *OutputFormat) Set(s string) error {
for _, of := range []OutputFormat{fmtJSON, fmtYAML} {

@ -31,13 +31,13 @@ const (
// Uninstall uses Kubernetes client to uninstall Tiller.
func Uninstall(client kubernetes.Interface, opts *Options) error {
if err := deleteService(client.Core(), opts.Namespace); err != nil {
if err := deleteService(client.CoreV1(), opts.Namespace); err != nil {
return err
}
if err := deleteDeployment(client, opts.Namespace); err != nil {
return err
}
return deleteSecret(client.Core(), opts.Namespace)
return deleteSecret(client.CoreV1(), opts.Namespace)
}
// deleteService deletes the Tiller Service resource
@ -47,10 +47,11 @@ func deleteService(client corev1.ServicesGetter, namespace string) error {
}
// deleteDeployment deletes the Tiller Deployment resource
// We need to use the reaper instead of the kube API because GC for deployment dependents
// is not yet supported at the k8s server level (<= 1.5)
func deleteDeployment(client kubernetes.Interface, namespace string) error {
err := client.Extensions().Deployments(namespace).Delete(deploymentName, &metav1.DeleteOptions{})
policy := metav1.DeletePropagationBackground
err := client.AppsV1().Deployments(namespace).Delete(deploymentName, &metav1.DeleteOptions{
PropagationPolicy: &policy,
})
return ingoreNotFound(err)
}

@ -61,7 +61,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
}
cmd := &cobra.Command{
Use: "lint [flags] PATH",
Short: "examines a chart for possible issues",
Short: "Examines a chart for possible issues",
Long: longLintHelp,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
@ -71,12 +71,12 @@ func newLintCmd(out io.Writer) *cobra.Command {
},
}
cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
cmd.Flags().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.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().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.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.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")
return cmd
}
@ -166,7 +166,7 @@ func lintChart(path string, vals []byte, namespace string, strict bool) (support
chartPath = path
}
// Guard: Error out of this is not a chart.
// Guard: Error out if this is not a chart.
if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
return linter, errLintNoChart
}
@ -177,7 +177,7 @@ func lintChart(path string, vals []byte, namespace string, strict bool) (support
// 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.
// This func is implemented intentionally and separately from the `vals` func for the `install` and `upgrade` commands.
// 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) {

@ -104,7 +104,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "list [flags] [FILTER]",
Short: "list releases",
Short: "List releases",
Long: listHelp,
Aliases: []string{"ls"},
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
@ -121,21 +121,21 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
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.sortDesc, "reverse", "r", false, "reverse the sort order")
f.IntVarP(&list.limit, "max", "m", 256, "maximum number of releases to fetch")
f.StringVarP(&list.offset, "offset", "o", "", "next release name in the list, used to offset from start value")
f.BoolVarP(&list.all, "all", "a", false, "show all releases, not just the ones marked DEPLOYED")
f.BoolVar(&list.deleted, "deleted", false, "show deleted releases")
f.BoolVar(&list.deleting, "deleting", false, "show releases that are currently being deleted")
f.BoolVar(&list.deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
f.BoolVar(&list.failed, "failed", false, "show failed releases")
f.BoolVar(&list.pending, "pending", false, "show pending releases")
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.StringVar(&list.output, "output", "", "output the specified format (json or yaml)")
f.BoolVarP(&list.byChartName, "chart-name", "c", false, "sort by chart name")
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.sortDesc, "reverse", "r", false, "Reverse the sort order")
f.IntVarP(&list.limit, "max", "m", 256, "Maximum number of releases to fetch")
f.StringVarP(&list.offset, "offset", "o", "", "Next release name in the list, used to offset from start value")
f.BoolVarP(&list.all, "all", "a", false, "Show all releases, not just the ones marked DEPLOYED")
f.BoolVar(&list.deleted, "deleted", false, "Show deleted releases")
f.BoolVar(&list.deleting, "deleting", false, "Show releases that are currently being deleted")
f.BoolVar(&list.deployed, "deployed", false, "Show deployed releases. If no other is specified, this will be automatically enabled")
f.BoolVar(&list.failed, "failed", false, "Show failed releases")
f.BoolVar(&list.pending, "pending", false, "Show pending releases")
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.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'?
//f.BoolVar(&list.superseded, "history", true, "show historical releases")

@ -70,7 +70,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "package [flags] [CHART_PATH] [...]",
Short: "package a chart directory into a chart archive",
Short: "Package a chart directory into a chart archive",
Long: packageDesc,
RunE: func(cmd *cobra.Command, args []string) error {
pkg.home = settings.Home
@ -96,14 +96,14 @@ func newPackageCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVar(&pkg.save, "save", true, "save packaged chart to local chart repository")
f.BoolVar(&pkg.sign, "sign", false, "use a PGP private key to sign this package")
f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true")
f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&pkg.appVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&pkg.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "requirements.yaml" to dir "charts/" before packaging`)
f.BoolVar(&pkg.save, "save", true, "Save packaged chart to local chart repository")
f.BoolVar(&pkg.sign, "sign", false, "Use a PGP private key to sign this package")
f.StringVar(&pkg.key, "key", "", "Name of the key to use when signing. Used if --sign is true")
f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "Location of a public keyring")
f.StringVar(&pkg.version, "version", "", "Set the version on the chart to this semver version")
f.StringVar(&pkg.appVersion, "app-version", "", "Set the appVersion on the chart to this version")
f.StringVarP(&pkg.destination, "destination", "d", ".", "Location to write the chart.")
f.BoolVarP(&pkg.dependencyUpdate, "dependency-update", "u", false, `Update dependencies from "requirements.yaml" to dir "charts/" before packaging`)
return cmd
}

@ -33,7 +33,7 @@ Manage client-side Helm plugins.
func newPluginCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "plugin",
Short: "add, list, or remove Helm plugins",
Short: "Add, list, or remove Helm plugins",
Long: pluginHelp,
}
cmd.AddCommand(

@ -44,7 +44,7 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command {
pcmd := &pluginInstallCmd{out: out}
cmd := &cobra.Command{
Use: "install [options] <path|url>...",
Short: "install one or more Helm plugins",
Short: "Install one or more Helm plugins",
Long: pluginInstallDesc,
PreRunE: func(cmd *cobra.Command, args []string) error {
return pcmd.complete(args)
@ -53,7 +53,7 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command {
return pcmd.run()
},
}
cmd.Flags().StringVar(&pcmd.version, "version", "", "specify a version constraint. If this is not specified, the latest version is installed")
cmd.Flags().StringVar(&pcmd.version, "version", "", "Specify a version constraint. If this is not specified, the latest version is installed")
return cmd
}

@ -34,7 +34,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
pcmd := &pluginListCmd{out: out}
cmd := &cobra.Command{
Use: "list",
Short: "list installed Helm plugins",
Short: "List installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error {
pcmd.home = settings.Home
return pcmd.run()

@ -38,7 +38,7 @@ func newPluginRemoveCmd(out io.Writer) *cobra.Command {
pcmd := &pluginRemoveCmd{out: out}
cmd := &cobra.Command{
Use: "remove <plugin>...",
Short: "remove one or more Helm plugins",
Short: "Remove one or more Helm plugins",
PreRunE: func(cmd *cobra.Command, args []string) error {
return pcmd.complete(args)
},

@ -39,7 +39,7 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
pcmd := &pluginUpdateCmd{out: out}
cmd := &cobra.Command{
Use: "update <plugin>...",
Short: "update one or more Helm plugins",
Short: "Update one or more Helm plugins",
PreRunE: func(cmd *cobra.Command, args []string) error {
return pcmd.complete(args)
},

@ -17,16 +17,31 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"text/template"
"time"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/timeconv"
)
type outputFormat string
const (
outputFlag = "output"
outputTable outputFormat = "table"
outputJSON outputFormat = "json"
outputYAML outputFormat = "yaml"
)
var printReleaseTemplate = `REVISION: {{.Release.Version}}
RELEASED: {{.ReleaseDate}}
CHART: {{.Release.Chart.Metadata.Name}}-{{.Release.Chart.Metadata.Version}}
@ -66,7 +81,7 @@ func printRelease(out io.Writer, rel *release.Release) error {
return tpl(printReleaseTemplate, data, out)
}
func tpl(t string, vals map[string]interface{}, out io.Writer) error {
func tpl(t string, vals interface{}, out io.Writer) error {
tt, err := template.New("_").Parse(t)
if err != nil {
return err
@ -80,3 +95,66 @@ func debug(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
}
// bindOutputFlag will add the output flag to the given command and bind the
// value to the given string pointer
func bindOutputFlag(cmd *cobra.Command, varRef *string) {
cmd.Flags().StringVarP(varRef, outputFlag, "o", string(outputTable), fmt.Sprintf("Prints the output in the specified format. Allowed values: %s, %s, %s", outputTable, outputJSON, outputYAML))
}
type outputWriter interface {
WriteTable(out io.Writer) error
WriteJSON(out io.Writer) error
WriteYAML(out io.Writer) error
}
func write(out io.Writer, ow outputWriter, format outputFormat) error {
switch format {
case outputTable:
return ow.WriteTable(out)
case outputJSON:
return ow.WriteJSON(out)
case outputYAML:
return ow.WriteYAML(out)
}
return fmt.Errorf("unsupported format %s", format)
}
// encodeJSON is a helper function to decorate any error message with a bit more
// context and avoid writing the same code over and over for printers
func encodeJSON(out io.Writer, obj interface{}) error {
enc := json.NewEncoder(out)
err := enc.Encode(obj)
if err != nil {
return fmt.Errorf("unable to write JSON output: %s", err)
}
return nil
}
// encodeYAML is a helper function to decorate any error message with a bit more
// context and avoid writing the same code over and over for printers
func encodeYAML(out io.Writer, obj interface{}) error {
raw, err := yaml.Marshal(obj)
if err != nil {
return fmt.Errorf("unable to write YAML output: %s", err)
}
// Append a newline, as with a JSON encoder
raw = append(raw, []byte("\n")...)
_, err = out.Write(raw)
if err != nil {
return fmt.Errorf("unable to write YAML output: %s", err)
}
return nil
}
// encodeTable is a helper function to decorate any error message with a bit
// more context and avoid writing the same code over and over for printers
func encodeTable(out io.Writer, table *uitable.Table) error {
raw := table.Bytes()
raw = append(raw, []byte("\n")...)
_, err := out.Write(raw)
if err != nil {
return fmt.Errorf("unable to write table output: %s", err)
}
return nil
}

@ -50,7 +50,7 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "test [RELEASE]",
Short: "test a release",
Short: "Test a release",
Long: releaseTestDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -66,9 +66,9 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
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.BoolVar(&rlsTest.cleanup, "cleanup", false, "delete test pods upon completion")
f.BoolVar(&rlsTest.parallel, "parallel", false, "run test pods in parallel")
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.parallel, "parallel", false, "Run test pods in parallel")
// set defaults from environment
settings.InitTLS(f)

@ -33,7 +33,7 @@ Example usage:
func newRepoCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "repo [FLAGS] add|remove|list|index|update [ARGS]",
Short: "add, list, remove, update, and index chart repositories",
Short: "Add, list, remove, update, and index chart repositories",
Long: repoHelm,
}

@ -17,16 +17,20 @@ limitations under the License.
package main
import (
"context"
"fmt"
"io"
"syscall"
"time"
"golang.org/x/crypto/ssh/terminal"
"github.com/gofrs/flock"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo"
"syscall"
)
type repoAddCmd struct {
@ -49,7 +53,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "add [flags] [NAME] [URL]",
Short: "add a chart repository",
Short: "Add a chart repository",
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "name for the chart repository", "the url of the chart repository"); err != nil {
return err
@ -64,12 +68,12 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&add.username, "username", "", "chart repository username")
f.StringVar(&add.password, "password", "", "chart repository password")
f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered")
f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&add.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.StringVar(&add.username, "username", "", "Chart repository username")
f.StringVar(&add.password, "password", "", "Chart repository password")
f.BoolVar(&add.noupdate, "no-update", false, "Raise error if repo is already registered")
f.StringVar(&add.certFile, "cert-file", "", "Identify HTTPS client using this SSL certificate file")
f.StringVar(&add.keyFile, "key-file", "", "Identify HTTPS client using this SSL key file")
f.StringVar(&add.caFile, "ca-file", "", "Verify certificates of HTTPS-enabled servers using this CA bundle")
return cmd
}
@ -131,6 +135,25 @@ func addRepository(name, url, username, password string, home helmpath.Home, cer
return fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", url, err.Error())
}
// Lock the repository file for concurrent goroutines or processes synchronization
fileLock := flock.New(home.RepositoryFile())
lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
locked, err := fileLock.TryLockContext(lockCtx, time.Second)
if err == nil && locked {
defer fileLock.Unlock()
}
if err != nil {
return err
}
// Re-read the repositories file before updating it as its content may have been changed
// by a concurrent execution after the first read and before being locked
f, err = repo.LoadRepositoriesFile(home.RepositoryFile())
if err != nil {
return err
}
f.Update(&c)
return f.WriteFile(home.RepositoryFile(), 0644)

@ -17,13 +17,18 @@ limitations under the License.
package main
import (
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/repo/repotest"
)
@ -101,3 +106,111 @@ func TestRepoAdd(t *testing.T) {
t.Errorf("Duplicate repository name was added")
}
}
func TestRepoAddConcurrentGoRoutines(t *testing.T) {
ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
ts.Stop()
os.RemoveAll(thome.String())
cleanup()
}()
settings.Home = thome
if err := ensureTestHome(settings.Home, t); err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func(name string) {
defer wg.Done()
if err := addRepository(name, ts.URL(), "", "", settings.Home, "", "", "", true); err != nil {
t.Error(err)
}
}(fmt.Sprintf("%s-%d", testName, i))
}
wg.Wait()
f, err := repo.LoadRepositoriesFile(settings.Home.RepositoryFile())
if err != nil {
t.Error(err)
}
var name string
for i := 0; i < 3; i++ {
name = fmt.Sprintf("%s-%d", testName, i)
if !f.Has(name) {
t.Errorf("%s was not successfully inserted into %s", name, settings.Home.RepositoryFile())
}
}
}
// Same as TestRepoAddConcurrentGoRoutines but with repository additions in sub-processes
func TestRepoAddConcurrentSubProcesses(t *testing.T) {
goWantHelperProcess := os.Getenv("GO_WANT_HELPER_PROCESS")
if goWantHelperProcess == "" {
// parent
ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
settings.Home = thome
cleanup := resetEnv()
defer func() {
ts.Stop()
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(settings.Home, t); err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func(name string) {
defer wg.Done()
cmd := exec.Command(os.Args[0], "-test.run=^TestRepoAddConcurrentSubProcesses$")
cmd.Env = append(os.Environ(), fmt.Sprintf("GO_WANT_HELPER_PROCESS=%s,%s", name, ts.URL()), fmt.Sprintf("HELM_HOME=%s", settings.Home))
out, err := cmd.CombinedOutput()
if len(out) > 0 || err != nil {
t.Fatalf("child process: %q, %v", out, err)
}
}(fmt.Sprintf("%s-%d", testName, i))
}
wg.Wait()
f, err := repo.LoadRepositoriesFile(settings.Home.RepositoryFile())
if err != nil {
t.Error(err)
}
var name string
for i := 0; i < 2; i++ {
name = fmt.Sprintf("%s-%d", testName, i)
if !f.Has(name) {
t.Errorf("%s was not successfully inserted into %s", name, settings.Home.RepositoryFile())
}
}
} else {
// child
s := strings.Split(goWantHelperProcess, ",")
settings.Home = helmpath.Home(os.Getenv("HELM_HOME"))
repoName := s[0]
tsURL := s[1]
if err := addRepository(repoName, tsURL, "", "", settings.Home, "", "", "", true); err != nil {
t.Fatal(err)
}
os.Exit(0)
}
}

@ -50,7 +50,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "index [flags] [DIR]",
Short: "generate an index file given a directory containing packaged charts",
Short: "Generate an index file given a directory containing packaged charts",
Long: repoIndexDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "path to a directory"); err != nil {
@ -64,8 +64,8 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&index.url, "url", "", "url of chart repository")
f.StringVar(&index.merge, "merge", "", "merge the generated index into the given index")
f.StringVar(&index.url, "url", "", "URL of the chart repository")
f.StringVar(&index.merge, "merge", "", "Merge the generated index into the given index")
return cmd
}

@ -17,8 +17,6 @@ limitations under the License.
package main
import (
"errors"
"fmt"
"io"
"github.com/gosuri/uitable"
@ -29,8 +27,14 @@ import (
)
type repoListCmd struct {
out io.Writer
home helmpath.Home
out io.Writer
home helmpath.Home
output string
}
type repositoryElement struct {
Name string
URL string
}
func newRepoListCmd(out io.Writer) *cobra.Command {
@ -38,29 +42,63 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "list [flags]",
Short: "list chart repositories",
Short: "List chart repositories",
RunE: func(cmd *cobra.Command, args []string) error {
list.home = settings.Home
return list.run()
},
}
bindOutputFlag(cmd, &list.output)
return cmd
}
func (a *repoListCmd) run() error {
f, err := repo.LoadRepositoriesFile(a.home.RepositoryFile())
repoFile, err := repo.LoadRepositoriesFile(a.home.RepositoryFile())
if err != nil {
return err
}
if len(f.Repositories) == 0 {
return errors.New("no repositories to show")
}
return write(a.out, &repoListWriter{repoFile.Repositories}, outputFormat(a.output))
}
//////////// Printer implementation below here
type repoListWriter struct {
repos []*repo.Entry
}
func (r *repoListWriter) WriteTable(out io.Writer) error {
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range f.Repositories {
for _, re := range r.repos {
table.AddRow(re.Name, re.URL)
}
fmt.Fprintln(a.out, table)
return encodeTable(out, table)
}
func (r *repoListWriter) WriteJSON(out io.Writer) error {
return r.encodeByFormat(out, outputJSON)
}
func (r *repoListWriter) WriteYAML(out io.Writer) error {
return r.encodeByFormat(out, outputYAML)
}
func (r *repoListWriter) encodeByFormat(out io.Writer, format outputFormat) error {
var repolist []repositoryElement
for _, re := range r.repos {
repolist = append(repolist, repositoryElement{Name: re.Name, URL: re.URL})
}
switch format {
case outputJSON:
return encodeJSON(out, repolist)
case outputYAML:
return encodeYAML(out, repolist)
}
// Because this is a non-exported function and only called internally by
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}

@ -39,7 +39,7 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "remove [flags] [NAME]",
Aliases: []string{"rm"},
Short: "remove a chart repository",
Short: "Remove a chart repository",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("need at least one argument, name of chart repository")

@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo"
@ -35,15 +36,24 @@ Information is cached locally, where it is used by commands like 'helm search'.
'helm update' is the deprecated form of 'helm repo update'. It will be removed in
future releases.
You can specify the name of a repository you want to update.
$ helm repo update <repo_name>
To update all the repositories, use 'helm repo update'.
`
var errNoRepositories = errors.New("no repositories found. You must add one before updating")
var errNoRepositoriesMatchingRepoName = errors.New("no repositories found matching the provided name. Verify if the repo exists")
type repoUpdateCmd struct {
update func([]*repo.ChartRepository, io.Writer, helmpath.Home, bool) error
home helmpath.Home
out io.Writer
strict bool
name string
}
func newRepoUpdateCmd(out io.Writer) *cobra.Command {
@ -52,18 +62,21 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
update: updateCharts,
}
cmd := &cobra.Command{
Use: "update",
Use: "update [REPO_NAME]",
Aliases: []string{"up"},
Short: "update information of available charts locally from chart repositories",
Short: "Update information of available charts locally from chart repositories",
Long: updateDesc,
RunE: func(cmd *cobra.Command, args []string) error {
u.home = settings.Home
if len(args) != 0 {
u.name = args[0]
}
return u.run()
},
}
f := cmd.Flags()
f.BoolVar(&u.strict, "strict", false, "fail on update warnings")
f.BoolVar(&u.strict, "strict", false, "Fail on update warnings")
return cmd
}
@ -83,8 +96,22 @@ func (u *repoUpdateCmd) run() error {
if err != nil {
return err
}
repos = append(repos, r)
if len(u.name) != 0 {
if cfg.Name == u.name {
repos = append(repos, r)
break
} else {
continue
}
} else {
repos = append(repos, r)
}
}
if len(repos) == 0 {
return errNoRepositoriesMatchingRepoName
}
return u.update(repos, u.out, u.home, u.strict)
}
@ -93,21 +120,28 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer, home helmpath.Ho
var (
errorCounter int
wg sync.WaitGroup
mu sync.Mutex
)
for _, re := range repos {
wg.Add(1)
go func(re *repo.ChartRepository) {
defer wg.Done()
if re.Config.Name == localRepository {
if re.Config.Name == installer.LocalRepository {
mu.Lock()
fmt.Fprintf(out, "...Skip %s chart repository\n", re.Config.Name)
mu.Unlock()
return
}
err := re.DownloadIndexFile(home.Cache())
if err != nil {
mu.Lock()
errorCounter++
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
mu.Unlock()
} else {
mu.Lock()
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)
mu.Unlock()
}
}(re)
}
@ -117,6 +151,6 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer, home helmpath.Ho
return errors.New("Update Failed. Check log for details")
}
fmt.Fprintln(out, "Update Complete. ⎈ Happy Helming!⎈ ")
fmt.Fprintln(out, "Update Complete.")
return nil
}

@ -105,3 +105,105 @@ func TestUpdateCharts(t *testing.T) {
t.Error("Update was not successful")
}
}
func TestUpdateCmdStrictFlag(t *testing.T) {
thome, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
os.RemoveAll(thome.String())
cleanup()
}()
settings.Home = thome
out := bytes.NewBuffer(nil)
cmd := newRepoUpdateCmd(out)
cmd.ParseFlags([]string{"--strict"})
if err := cmd.RunE(cmd, []string{}); err == nil {
t.Fatal("expected error due to strict flag")
}
if got := out.String(); !strings.Contains(got, "Unable to get an update") {
t.Errorf("Expected 'Unable to get an update', got %q", got)
}
}
func TestUpdateCmdWithSingleRepoNameWhichDoesntExist(t *testing.T) {
thome, err := tempHelmHome(t)
if err != nil {
t.Fatal(err)
}
cleanup := resetEnv()
defer func() {
os.RemoveAll(thome.String())
cleanup()
}()
settings.Home = thome
out := bytes.NewBuffer(nil)
cmd := newRepoUpdateCmd(out)
if err = cmd.RunE(cmd, []string{"randomRepo"}); err == nil {
t.Fatal("expected error due to wrong repo name")
}
if got := fmt.Sprintf("%v", err); !strings.Contains(got, "no repositories found matching the provided name. Verify if the repo exists") {
t.Errorf("Expected 'no repositories found matching the provided name. Verify if the repo exists', got %q", got)
}
}
func TestUpdateRepo(t *testing.T) {
ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
hh := helmpath.Home(thome)
cleanup := resetEnv()
defer func() {
ts.Stop()
os.RemoveAll(thome.String())
cleanup()
}()
if err := ensureTestHome(hh, t); err != nil {
t.Fatal(err)
}
settings.Home = thome
if err := addRepository("repo1", ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
if err := addRepository("repo2", ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
out := bytes.NewBuffer(nil)
cmd := newRepoUpdateCmd(out)
if err = cmd.RunE(cmd, []string{"repo1"}); err != nil {
t.Fatal("expected to update repo1 correctly")
}
got := out.String()
if !strings.Contains(got, "Successfully got an update from the \"repo1\"") {
t.Errorf("Expected to successfully update \"repo1\" repository, got %q", got)
}
if strings.Contains(got, "Successfully got an update from the \"repo2\"") {
t.Errorf("Shouldn't have updated \"repo2\" repository, got %q", got)
}
if !strings.Contains(got, "Update Complete.") {
t.Error("Update was not successful")
}
}

@ -21,6 +21,7 @@ import (
"fmt"
"io"
"os"
"strings"
"github.com/spf13/cobra"
@ -56,10 +57,14 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "reset",
Short: "uninstalls Tiller from a cluster",
Short: "Uninstalls Tiller from a cluster",
Long: resetDesc,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := setupConnection(); !d.force && err != nil {
err := setupConnection()
if !d.force && err != nil {
return err
}
if d.force && err != nil && strings.EqualFold(err.Error(), "could not find tiller") {
return err
}
return nil
@ -79,8 +84,8 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
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.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME")
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")
// set defaults from environment
settings.InitTLS(f)
@ -110,7 +115,7 @@ func (d *resetCmd) run() error {
}
if err := installer.Uninstall(d.kubeClient, &installer.Options{Namespace: d.namespace}); err != nil {
return fmt.Errorf("error unstalling Tiller: %s", err)
return fmt.Errorf("error uninstalling Tiller: %s", err)
}
if d.removeHelmHome {

@ -31,21 +31,23 @@ This command rolls back a release to a previous revision.
The first argument of the rollback command is the name of a release, and the
second is a revision (version) number. To see revision numbers, run
'helm history RELEASE'.
'helm history RELEASE'. If you'd like to rollback to the previous release use
'helm rollback [RELEASE] 0'.
`
type rollbackCmd struct {
name string
revision int32
dryRun bool
recreate bool
force bool
disableHooks bool
out io.Writer
client helm.Interface
timeout int64
wait bool
description string
name string
revision int32
dryRun bool
recreate bool
force bool
disableHooks bool
out io.Writer
client helm.Interface
timeout int64
wait bool
description string
cleanupOnFail bool
}
func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
@ -56,7 +58,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "rollback [flags] [RELEASE] [REVISION]",
Short: "roll back a release to a previous revision",
Short: "Rollback a release to a previous revision",
Long: rollbackDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -79,13 +81,14 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
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.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.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.StringVar(&rollback.description, "description", "", "specify a description for the release")
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.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.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.StringVar(&rollback.description, "description", "", "Specify a description for the release")
f.BoolVar(&rollback.cleanupOnFail, "cleanup-on-fail", false, "Allow deletion of new resources created in this rollback when rollback failed")
// set defaults from environment
settings.InitTLS(f)
@ -103,12 +106,13 @@ func (r *rollbackCmd) run() error {
helm.RollbackVersion(r.revision),
helm.RollbackTimeout(r.timeout),
helm.RollbackWait(r.wait),
helm.RollbackDescription(r.description))
helm.RollbackDescription(r.description),
helm.RollbackCleanupOnFail(r.cleanupOnFail))
if err != nil {
return prettyError(err)
}
fmt.Fprintf(r.out, "Rollback was a success! Happy Helming!\n")
fmt.Fprintf(r.out, "Rollback was a success.\n")
return nil
}

@ -31,25 +31,25 @@ func TestRollbackCmd(t *testing.T) {
{
name: "rollback a release",
args: []string{"funny-honey", "1"},
expected: "Rollback was a success! Happy Helming!",
expected: "Rollback was a success.",
},
{
name: "rollback a release with timeout",
args: []string{"funny-honey", "1"},
flags: []string{"--timeout", "120"},
expected: "Rollback was a success! Happy Helming!",
expected: "Rollback was a success.",
},
{
name: "rollback a release with wait",
args: []string{"funny-honey", "1"},
flags: []string{"--wait"},
expected: "Rollback was a success! Happy Helming!",
expected: "Rollback was a success.",
},
{
name: "rollback a release with description",
args: []string{"funny-honey", "1"},
flags: []string{"--description", "foo"},
expected: "Rollback was a success! Happy Helming!",
expected: "Rollback was a success.",
},
{
name: "rollback a release without revision",

@ -48,6 +48,14 @@ type searchCmd struct {
regexp bool
version string
colWidth uint
output string
}
type chartElement struct {
Name string
Version string
AppVersion string
Description string
}
func newSearchCmd(out io.Writer) *cobra.Command {
@ -55,7 +63,7 @@ func newSearchCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "search [keyword]",
Short: "search for a keyword in charts",
Short: "Search for a keyword in charts",
Long: searchDesc,
RunE: func(cmd *cobra.Command, args []string) error {
sc.helmhome = settings.Home
@ -64,10 +72,11 @@ func newSearchCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVarP(&sc.regexp, "regexp", "r", false, "use regular expressions for searching")
f.BoolVarP(&sc.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line")
f.StringVarP(&sc.version, "version", "v", "", "search using semantic versioning constraints")
f.UintVar(&sc.colWidth, "col-width", 60, "specifies the max column width of output")
f.BoolVarP(&sc.regexp, "regexp", "r", false, "Use regular expressions for searching")
f.BoolVarP(&sc.versions, "versions", "l", false, "Show the long listing, with each version of each chart on its own line")
f.StringVarP(&sc.version, "version", "v", "", "Search using semantic versioning constraints")
f.UintVar(&sc.colWidth, "col-width", 60, "Specifies the max column width of output")
bindOutputFlag(cmd, &sc.output)
return cmd
}
@ -95,9 +104,7 @@ func (s *searchCmd) run(args []string) error {
return err
}
fmt.Fprintln(s.out, s.formatSearchResults(data, s.colWidth))
return nil
return write(s.out, &searchWriter{data, s.colWidth}, outputFormat(s.output))
}
func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, error) {
@ -128,19 +135,6 @@ func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, err
return data, nil
}
func (s *searchCmd) formatSearchResults(res []*search.Result, colWidth uint) string {
if len(res) == 0 {
return "No results found"
}
table := uitable.New()
table.MaxColWidth = colWidth
table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
for _, r := range res {
table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
}
return table.String()
}
func (s *searchCmd) buildIndex() (*search.Index, error) {
// Load the repositories.yaml
rf, err := repo.LoadRepositoriesFile(s.helmhome.RepositoryFile())
@ -154,7 +148,7 @@ func (s *searchCmd) buildIndex() (*search.Index, error) {
f := s.helmhome.CacheIndex(n)
ind, err := repo.LoadIndexFile(f)
if err != nil {
fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n)
fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.\n", n)
continue
}
@ -162,3 +156,53 @@ func (s *searchCmd) buildIndex() (*search.Index, error) {
}
return i, nil
}
//////////// Printer implementation below here
type searchWriter struct {
results []*search.Result
columnWidth uint
}
func (r *searchWriter) WriteTable(out io.Writer) error {
if len(r.results) == 0 {
_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
}
return nil
}
table := uitable.New()
table.MaxColWidth = r.columnWidth
table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
for _, r := range r.results {
table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
}
return encodeTable(out, table)
}
func (r *searchWriter) WriteJSON(out io.Writer) error {
return r.encodeByFormat(out, outputJSON)
}
func (r *searchWriter) WriteYAML(out io.Writer) error {
return r.encodeByFormat(out, outputYAML)
}
func (r *searchWriter) encodeByFormat(out io.Writer, format outputFormat) error {
var chartList []chartElement
for _, r := range r.results {
chartList = append(chartList, chartElement{r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description})
}
switch format {
case outputJSON:
return encodeJSON(out, chartList)
case outputYAML:
return encodeYAML(out, chartList)
}
// Because this is a non-exported function and only called internally by
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}

@ -33,6 +33,12 @@ import (
"k8s.io/helm/pkg/repo"
)
const (
sep = "\v"
// verSep is a separator for version fields in map keys.
verSep = "$$"
)
// Result is a search result.
//
// Score indicates how close it is to match. The higher the score, the longer
@ -49,16 +55,11 @@ type Index struct {
charts map[string]*repo.ChartVersion
}
const sep = "\v"
// NewIndex creats a new Index.
// NewIndex creates a new Index.
func NewIndex() *Index {
return &Index{lines: map[string]string{}, charts: map[string]*repo.ChartVersion{}}
}
// verSep is a separator for version fields in map keys.
const verSep = "$$"
// AddRepo adds a repository index to the search index.
func (i *Index) AddRepo(rname string, ind *repo.IndexFile, all bool) {
ind.SortEntries()

@ -18,6 +18,7 @@ package main
import (
"io"
"strings"
"testing"
"github.com/spf13/cobra"
@ -84,6 +85,30 @@ func TestSearchCmd(t *testing.T) {
flags: []string{"--regexp"},
err: true,
},
{
name: "search for 'maria', expect one match output json",
args: []string{"maria"},
flags: strings.Split("--output json", " "),
expected: `[{"Name":"testing/mariadb","Version":"0.3.0","Appversion":"","Description":"Chart for MariaDB"}]`,
},
{
name: "search for 'alpine', expect two matches output json",
args: []string{"alpine"},
flags: strings.Split("--output json", " "),
expected: `[{"Name":"testing/alpine","Version":"0.2.0","Appversion":"2.3.4","Description":"Deploy a basic Alpine Linux pod"}]`,
},
{
name: "search for 'maria', expect one match output yaml",
args: []string{"maria"},
flags: strings.Split("--output yaml", " "),
expected: "- AppVersion: \"\"\n Description: Chart for MariaDB\n Name: testing/mariadb\n Version: 0.3.0\n\n",
},
{
name: "search for 'alpine', expect two matches output yaml",
args: []string{"alpine"},
flags: strings.Split("--output yaml", " "),
expected: "- AppVersion: 2.3.4\n Description: Deploy a basic Alpine Linux pod\n Name: testing/alpine\n Version: 0.2.0\n\n",
},
}
cleanup := resetEnv()

@ -53,7 +53,7 @@ func newServeCmd(out io.Writer) *cobra.Command {
srv := &serveCmd{out: out}
cmd := &cobra.Command{
Use: "serve",
Short: "start a local http web server",
Short: "Start a local http web server",
Long: serveDesc,
PreRunE: func(cmd *cobra.Command, args []string) error {
return srv.complete()
@ -64,9 +64,9 @@ func newServeCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&srv.repoPath, "repo-path", "", "local directory path from which to serve charts")
f.StringVar(&srv.address, "address", "127.0.0.1:8879", "address to listen on")
f.StringVar(&srv.url, "url", "", "external URL of chart repository")
f.StringVar(&srv.repoPath, "repo-path", "", "Local directory path from which to serve charts")
f.StringVar(&srv.address, "address", "127.0.0.1:8879", "Address to listen on")
f.StringVar(&srv.url, "url", "", "External URL of chart repository")
return cmd
}

@ -17,13 +17,11 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"regexp"
"text/tabwriter"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/gosuri/uitable/util/strutil"
"github.com/spf13/cobra"
@ -61,7 +59,7 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "status [flags] RELEASE_NAME",
Short: "displays the status of the named release",
Short: "Displays the status of the named release",
Long: statusHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -78,8 +76,8 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&status.version, "revision", 0, "if set, display the status of the named release with revision")
f.StringVarP(&status.outfmt, "output", "o", "", "output the status in the specified format (json or yaml)")
f.Int32Var(&status.version, "revision", 0, "If set, display the status of the named release with revision")
bindOutputFlag(cmd, &status.outfmt)
// set defaults from environment
settings.InitTLS(f)
@ -93,27 +91,26 @@ func (s *statusCmd) run() error {
return prettyError(err)
}
switch s.outfmt {
case "":
PrintStatus(s.out, res)
return nil
case "json":
data, err := json.Marshal(res)
if err != nil {
return fmt.Errorf("Failed to Marshal JSON output: %s", err)
}
s.out.Write(data)
return nil
case "yaml":
data, err := yaml.Marshal(res)
if err != nil {
return fmt.Errorf("Failed to Marshal YAML output: %s", err)
}
s.out.Write(data)
return nil
}
return write(s.out, &statusWriter{res}, outputFormat(s.outfmt))
}
type statusWriter struct {
status *services.GetReleaseStatusResponse
}
func (s *statusWriter) WriteTable(out io.Writer) error {
PrintStatus(out, s.status)
// There is no error handling here due to backwards compatibility with
// PrintStatus
return nil
}
func (s *statusWriter) WriteJSON(out io.Writer) error {
return encodeJSON(out, s.status)
}
return fmt.Errorf("Unknown output format %q", s.outfmt)
func (s *statusWriter) WriteYAML(out io.Writer) error {
return encodeYAML(out, s.status)
}
// PrintStatus prints out the status of a release. Shared because also used by

@ -86,24 +86,25 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "template [flags] CHART",
Short: fmt.Sprintf("locally render templates"),
Short: "Locally render templates",
Long: templateDesc,
RunE: t.run,
}
cmd.SetOutput(out)
f := cmd.Flags()
f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&t.releaseName, "name", "n", "release-name", "release name")
f.BoolVar(&t.releaseIsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into")
f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.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(&t.nameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&t.showNotes, "notes", false, "Show the computed NOTES.txt file as well")
f.StringVarP(&t.releaseName, "name", "n", "release-name", "Release name")
f.BoolVar(&t.releaseIsUpgrade, "is-upgrade", false, "Set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "Only execute the given templates")
f.VarP(&t.valueFiles, "values", "f", "Specify values in a YAML file (can specify multiple)")
f.StringVar(&t.namespace, "namespace", "", "Namespace to install the release into")
f.StringArrayVar(&t.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.stringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.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(&t.nameTemplate, "name-template", "", "Specify template used to name the release")
f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "Kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringVar(&t.outputDir, "output-dir", "", "Writes the executed templates to files in output-dir instead of stdout")
return cmd
}
@ -147,8 +148,8 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
}
}
if msgs := validation.IsDNS1123Label(t.releaseName); t.releaseName != "" && len(msgs) > 0 {
return fmt.Errorf("release name %s is not a valid DNS label: %s", t.releaseName, strings.Join(msgs, ";"))
if msgs := validation.IsDNS1123Subdomain(t.releaseName); t.releaseName != "" && len(msgs) > 0 {
return fmt.Errorf("release name %s is invalid: %s", t.releaseName, strings.Join(msgs, ";"))
}
// Check chart requirements to make sure all dependencies are present in /charts
@ -241,20 +242,20 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
if whitespaceRegex.MatchString(data) {
continue
}
err = writeToFile(t.outputDir, m.Name, data)
err = writeToFile(t.outputDir, m.Name, data, t.out)
if err != nil {
return err
}
continue
}
fmt.Printf("---\n# Source: %s\n", m.Name)
fmt.Println(data)
fmt.Fprintf(t.out, "---\n# Source: %s\n", m.Name)
fmt.Fprintln(t.out, data)
}
return nil
}
// write the <data> to <output-dir>/<name>
func writeToFile(outputDir string, name string, data string) error {
func writeToFile(outputDir string, name string, data string, out io.Writer) error {
outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
err := ensureDirectoryForFile(outfileName)
@ -275,7 +276,7 @@ func writeToFile(outputDir string, name string, data string) error {
return err
}
fmt.Printf("wrote %s\n", outfileName)
fmt.Fprintf(out, "wrote %s\n", outfileName)
return nil
}

@ -20,7 +20,6 @@ import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@ -112,21 +111,21 @@ func TestTemplateCmd(t *testing.T) {
desc: "verify the release name using capitals is invalid",
args: []string{subchart1ChartPath, "--name", "FOO"},
expectKey: "subchart1/templates/service.yaml",
expectError: "is not a valid DNS label",
expectError: "is invalid",
},
{
name: "check_invalid_name_uppercase",
desc: "verify the release name using periods is invalid",
args: []string{subchart1ChartPath, "--name", "foo.bar"},
expectKey: "subchart1/templates/service.yaml",
expectError: "is not a valid DNS label",
expectValue: "release-name: \"foo.bar\"",
},
{
name: "check_invalid_name_uppercase",
desc: "verify the release name using underscores is invalid",
args: []string{subchart1ChartPath, "--name", "foo_bar"},
expectKey: "subchart1/templates/service.yaml",
expectError: "is not a valid DNS label",
expectError: "is invalid",
},
{
name: "check_release_is_install",
@ -158,9 +157,9 @@ func TestTemplateCmd(t *testing.T) {
},
{
name: "check_invalid_name_template",
desc: "verify the relase name generate by template is invalid",
desc: "verify the release name generate by template is invalid",
args: []string{subchart1ChartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"},
expectError: "is not a valid DNS label",
expectError: "is invalid",
},
{
name: "check_name_template",
@ -178,14 +177,9 @@ func TestTemplateCmd(t *testing.T) {
},
}
var buf bytes.Buffer
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
// capture stdout
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
// execute template command
out := bytes.NewBuffer(nil)
cmd := newTemplateCmd(out)
@ -206,14 +200,8 @@ func TestTemplateCmd(t *testing.T) {
} else if err != nil {
t.Errorf("expected no error, got %v", err)
}
// restore stdout
w.Close()
os.Stdout = old
var b bytes.Buffer
io.Copy(&b, r)
r.Close()
// scan yaml into map[<path>]yaml
scanner := bufio.NewScanner(&b)
scanner := bufio.NewScanner(out)
next := false
lastKey := ""
m := map[string]string{}
@ -239,7 +227,6 @@ func TestTemplateCmd(t *testing.T) {
} else {
t.Errorf("could not find key %s", tt.expectKey)
}
buf.Reset()
})
}
}

@ -1,3 +1,4 @@
appVersion: "3.3"
description: Deploy a basic Alpine Linux pod
home: https://k8s.io/helm
name: alpine

@ -10,6 +10,7 @@ metadata:
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
app.kubernetes.io/instance: {{.Release.Name | quote }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
# This makes it easy to audit chart usage.
helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}"
values: {{.Values.test.Name}}

@ -4,3 +4,4 @@ name: novals
sources:
- https://github.com/helm/helm
version: 0.2.0
appVersion: 3.3

@ -10,6 +10,7 @@ metadata:
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
app.kubernetes.io/instance: {{.Release.Name | quote }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
# This makes it easy to audit chart usage.
helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:

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

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

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

@ -84,36 +84,40 @@ which results in "pwd: 3jk$o2z=f\30with'quote".
`
type upgradeCmd struct {
release string
chart string
out io.Writer
client helm.Interface
dryRun bool
recreate bool
force bool
disableHooks bool
valueFiles valueFiles
values []string
stringValues []string
fileValues []string
verify bool
keyring string
install bool
namespace string
version string
timeout int64
resetValues bool
reuseValues bool
wait bool
repoURL string
username string
password string
devel bool
description string
release string
chart string
out io.Writer
client helm.Interface
dryRun bool
recreate bool
force bool
disableHooks bool
valueFiles valueFiles
values []string
stringValues []string
fileValues []string
verify bool
keyring string
install bool
namespace string
version string
timeout int64
resetValues bool
reuseValues bool
wait bool
atomic bool
repoURL string
username string
password string
devel bool
subNotes bool
description string
cleanupOnFail bool
certFile string
keyFile string
caFile string
output string
}
func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
@ -125,7 +129,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]",
Short: "upgrade a release",
Short: "Upgrade a release",
Long: upgradeDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
@ -141,6 +145,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
upgrade.release = args[0]
upgrade.chart = args[1]
upgrade.client = ensureHelmClient(upgrade.client)
upgrade.wait = upgrade.wait || upgrade.atomic
return upgrade.run()
},
@ -148,34 +153,38 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed")
f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.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.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading")
f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "path to the keyring that contains public signing keys")
f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.StringVar(&upgrade.namespace, "namespace", "", "namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace")
f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used")
f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.StringVar(&upgrade.description, "description", "", "specify the description to use for the upgrade, rather than the default")
f.MarkDeprecated("disable-hooks", "use --no-hooks instead")
f.VarP(&upgrade.valueFiles, "values", "f", "Specify values in a YAML file or a URL(can specify multiple)")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "Simulate an upgrade")
f.BoolVar(&upgrade.recreate, "recreate-pods", false, "Performs pods restart for the resource if applicable")
f.BoolVar(&upgrade.force, "force", false, "Force resource update through delete/recreate if needed")
f.StringArrayVar(&upgrade.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.stringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.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.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "Disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "Disable pre/post upgrade hooks")
f.BoolVar(&upgrade.verify, "verify", false, "Verify the provenance of the chart before upgrading")
f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "Path to the keyring that contains public signing keys")
f.BoolVarP(&upgrade.install, "install", "i", false, "If a release by this name doesn't already exist, run an install")
f.StringVar(&upgrade.namespace, "namespace", "", "Namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace")
f.StringVar(&upgrade.version, "version", "", "Specify the exact chart version to use. If this is not specified, the latest version is used")
f.Int64Var(&upgrade.timeout, "timeout", 300, "Time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&upgrade.resetValues, "reset-values", false, "When upgrading, reset the values to the ones built into the chart")
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "When upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.wait, "wait", false, "If set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&upgrade.atomic, "atomic", false, "If set, upgrade process rolls back changes made in case of failed upgrade, also sets --wait flag")
f.StringVar(&upgrade.repoURL, "repo", "", "Chart repository url where to locate the requested chart")
f.StringVar(&upgrade.username, "username", "", "Chart repository username where to locate the requested chart")
f.StringVar(&upgrade.password, "password", "", "Chart repository password where to locate the requested chart")
f.StringVar(&upgrade.certFile, "cert-file", "", "Identify HTTPS client using this SSL certificate file")
f.StringVar(&upgrade.keyFile, "key-file", "", "Identify HTTPS client using this SSL key file")
f.StringVar(&upgrade.caFile, "ca-file", "", "Verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&upgrade.devel, "devel", false, "Use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&upgrade.subNotes, "render-subchart-notes", false, "Render subchart notes along with parent")
f.StringVar(&upgrade.description, "description", "", "Specify the description to use for the upgrade, rather than the default")
f.BoolVar(&upgrade.cleanupOnFail, "cleanup-on-fail", false, "Allow deletion of new resources created in this upgrade when upgrade failed")
bindOutputFlag(cmd, &upgrade.output)
f.MarkDeprecated("disable-hooks", "Use --no-hooks instead")
// set defaults from environment
settings.InitTLS(f)
@ -189,6 +198,8 @@ func (u *upgradeCmd) run() error {
return err
}
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if u.install {
// If a release does not exist, install it. If another error occurs during
// the check, ignore the error and continue with the upgrade.
@ -196,7 +207,6 @@ func (u *upgradeCmd) run() error {
// The returned error is a grpc.rpcError that wraps the message from the original error.
// So we're stuck doing string matching against the wrapped error, which is nested somewhere
// inside of the grpc.rpcError message.
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if err == nil {
if u.namespace == "" {
@ -230,6 +240,7 @@ func (u *upgradeCmd) run() error {
timeout: u.timeout,
wait: u.wait,
description: u.description,
atomic: u.atomic,
}
return ic.run()
}
@ -241,7 +252,8 @@ func (u *upgradeCmd) run() error {
}
// Check chart requirements to make sure all dependencies are present in /charts
if ch, err := chartutil.Load(chartPath); err == nil {
ch, err := chartutil.Load(chartPath)
if err == nil {
if req, err := chartutil.LoadRequirements(ch); err == nil {
if err := renderutil.CheckDependencies(ch, req); err != nil {
return err
@ -253,9 +265,9 @@ func (u *upgradeCmd) run() error {
return prettyError(err)
}
resp, err := u.client.UpdateRelease(
resp, err := u.client.UpdateReleaseFromChart(
u.release,
chartPath,
ch,
helm.UpdateValueOverrides(rawVals),
helm.UpgradeDryRun(u.dryRun),
helm.UpgradeRecreate(u.recreate),
@ -264,9 +276,32 @@ func (u *upgradeCmd) run() error {
helm.UpgradeTimeout(u.timeout),
helm.ResetValues(u.resetValues),
helm.ReuseValues(u.reuseValues),
helm.UpgradeSubNotes(u.subNotes),
helm.UpgradeWait(u.wait),
helm.UpgradeDescription(u.description))
helm.UpgradeDescription(u.description),
helm.UpgradeCleanupOnFail(u.cleanupOnFail))
if err != nil {
fmt.Fprintf(u.out, "UPGRADE FAILED\nError: %v\n", prettyError(err))
if u.atomic {
fmt.Fprint(u.out, "ROLLING BACK")
rollback := &rollbackCmd{
out: u.out,
client: u.client,
name: u.release,
dryRun: u.dryRun,
recreate: u.recreate,
force: u.force,
timeout: u.timeout,
wait: u.wait,
description: "",
revision: releaseHistory.Releases[0].Version,
disableHooks: u.disableHooks,
cleanupOnFail: u.cleanupOnFail,
}
if err := rollback.run(); err != nil {
return err
}
}
return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err))
}
@ -274,14 +309,14 @@ func (u *upgradeCmd) run() error {
printRelease(u.out, resp.Release)
}
fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release)
if outputFormat(u.output) == outputTable {
fmt.Fprintf(u.out, "Release %q has been upgraded.\n", u.release)
}
// Print the status like status command does
status, err := u.client.ReleaseStatus(u.release)
if err != nil {
return prettyError(err)
}
PrintStatus(u.out, status)
return nil
return write(u.out, &statusWriter{status}, outputFormat(u.output))
}

@ -96,7 +96,7 @@ func TestUpgradeCmd(t *testing.T) {
name: "upgrade a release",
args: []string{"funny-bunny", chartPath},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"funny-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch})},
},
{
@ -104,7 +104,7 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"funny-bunny", chartPath},
flags: []string{"--timeout", "120"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"funny-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2})},
},
{
@ -112,7 +112,7 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"funny-bunny", chartPath},
flags: []string{"--reset-values", "true"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"funny-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2})},
},
{
@ -120,15 +120,23 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"funny-bunny", chartPath},
flags: []string{"--reuse-values", "true"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"funny-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2})},
},
{
name: "install a release with 'upgrade --atomic'",
args: []string{"funny-bunny", chartPath},
flags: []string{"--atomic"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 6, Chart: ch}),
expected: "Release \"funny-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 6, Chart: ch})},
},
{
name: "install a release with 'upgrade --install'",
args: []string{"zany-bunny", chartPath},
flags: []string{"-i"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch}),
expected: "Release \"zany-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"zany-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch})},
},
{
@ -136,7 +144,7 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"crazy-bunny", chartPath},
flags: []string{"-i", "--timeout", "120"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch}),
expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"crazy-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch})},
},
{
@ -144,7 +152,7 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"crazy-bunny", chartPath},
flags: []string{"-i", "--description", "foo"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch, Description: "foo"}),
expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"crazy-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch, Description: "foo"})},
},
{
@ -152,7 +160,7 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"crazy-bunny", chartPath},
flags: []string{"--wait"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2}),
expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"crazy-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2})},
},
{
@ -160,7 +168,7 @@ func TestUpgradeCmd(t *testing.T) {
args: []string{"crazy-bunny", chartPath},
flags: []string{"--description", "foo"},
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2}),
expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n",
expected: "Release \"crazy-bunny\" has been upgraded.\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2, Description: "foo"})},
},
{

@ -27,7 +27,7 @@ import (
const verifyDesc = `
Verify that the given chart has a valid provenance file.
Provenance files provide crytographic verification that a chart has not been
Provenance files provide cryptographic verification that a chart has not been
tampered with, and was packaged by a trusted provider.
This command can be used to verify a local chart. Several other commands provide
@ -47,7 +47,7 @@ func newVerifyCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "verify [flags] PATH",
Short: "verify that a chart at the given path has been signed and is valid",
Short: "Verify that a chart at the given path has been signed and is valid",
Long: verifyDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
@ -59,7 +59,7 @@ func newVerifyCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&vc.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.StringVar(&vc.keyring, "keyring", defaultKeyring(), "Keyring containing public keys")
return cmd
}

@ -66,7 +66,7 @@ func newVersionCmd(c helm.Interface, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "print the client/server version information",
Short: "Print the client/server version information",
Long: versionDesc,
RunE: func(cmd *cobra.Command, args []string) error {
// If neither is explicitly set, show both.
@ -78,10 +78,10 @@ func newVersionCmd(c helm.Interface, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.BoolVarP(&version.showClient, "client", "c", false, "client version only")
f.BoolVarP(&version.showServer, "server", "s", false, "server version only")
f.BoolVar(&version.short, "short", false, "print the version number")
f.StringVar(&version.template, "template", "", "template for version string format")
f.BoolVarP(&version.showClient, "client", "c", false, "Client version only")
f.BoolVarP(&version.showServer, "server", "s", false, "Server version only")
f.BoolVar(&version.short, "short", false, "Print the version number")
f.StringVar(&version.template, "template", "", "Template for version string format")
// set defaults from environment
settings.InitTLS(f)
@ -151,5 +151,5 @@ func formatVersion(v *pb.Version, short bool) string {
if short && v.GitCommit != "" {
return fmt.Sprintf("%s+g%s", v.SemVer, v.GitCommit[:7])
}
return fmt.Sprintf("%#v", v)
return fmt.Sprintf("&version.Version{SemVer:\"%s\", GitCommit:\"%s\", GitTreeState:\"%s\"}", v.SemVer, v.GitCommit, v.GitTreeState)
}

@ -131,7 +131,13 @@ func (r *ReleaseModuleServiceServer) RollbackRelease(ctx context.Context, in *ru
grpclog.Print("rollback")
c := bytes.NewBufferString(in.Current.Manifest)
t := bytes.NewBufferString(in.Target.Manifest)
err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait)
err := kubeClient.UpdateWithOptions(in.Target.Namespace, c, t, kube.UpdateOptions{
Force: in.Force,
Recreate: in.Recreate,
Timeout: in.Timeout,
ShouldWait: in.Wait,
CleanupOnFail: in.CleanupOnFail,
})
return &rudderAPI.RollbackReleaseResponse{}, err
}
@ -140,7 +146,13 @@ func (r *ReleaseModuleServiceServer) UpgradeRelease(ctx context.Context, in *rud
grpclog.Print("upgrade")
c := bytes.NewBufferString(in.Current.Manifest)
t := bytes.NewBufferString(in.Target.Manifest)
err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait)
err := kubeClient.UpdateWithOptions(in.Target.Namespace, c, t, kube.UpdateOptions{
Force: in.Force,
Recreate: in.Recreate,
Timeout: in.Timeout,
ShouldWait: in.Wait,
CleanupOnFail: in.CleanupOnFail,
})
// upgrade response object should be changed to include status
return &rudderAPI.UpgradeReleaseResponse{}, err
}

@ -36,6 +36,7 @@ import (
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive"
"k8s.io/klog"
// Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth"
@ -65,8 +66,8 @@ const (
storageMemory = "memory"
storageConfigMap = "configmap"
storageSecret = "secret"
storageSQL = "sql"
probeAddr = ":44135"
traceAddr = ":44136"
// defaultMaxHistory sets the maximum number of releases to 0: unlimited
@ -74,17 +75,23 @@ const (
)
var (
grpcAddr = flag.String("listen", ":44134", "address:port to listen on")
enableTracing = flag.Bool("trace", false, "enable rpc tracing")
store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap', 'memory', or 'secret'")
grpcAddr = flag.String("listen", fmt.Sprintf(":%v", environment.DefaultTillerPort), "address:port to listen on")
probeAddr = flag.String("probe-listen", fmt.Sprintf(":%v", environment.DefaultTillerProbePort), "address:port to listen on for probes")
enableTracing = flag.Bool("trace", false, "enable rpc tracing")
store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap', 'memory', 'sql' or 'secret'")
sqlDialect = flag.String("sql-dialect", "postgres", "SQL dialect to use (only postgres is supported for now")
sqlConnectionString = flag.String("sql-connection-string", "", "SQL connection string to use")
remoteReleaseModules = flag.Bool("experimental-release", false, "enable experimental release modules")
tlsEnable = flag.Bool("tls", tlsEnableEnvVarDefault(), "enable TLS")
tlsVerify = flag.Bool("tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate")
keyFile = flag.String("tls-key", tlsDefaultsFromEnv("tls-key"), "path to TLS private key file")
certFile = flag.String("tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file")
caCertFile = flag.String("tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA")
maxHistory = flag.Int("history-max", historyMaxFromEnv(), "maximum number of releases kept in release history, with 0 meaning no limit")
printVersion = flag.Bool("version", false, "print the version number")
tlsEnable = flag.Bool("tls", tlsEnableEnvVarDefault(), "enable TLS")
tlsVerify = flag.Bool("tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate")
keyFile = flag.String("tls-key", tlsDefaultsFromEnv("tls-key"), "path to TLS private key file")
certFile = flag.String("tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file")
caCertFile = flag.String("tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA")
maxHistory = flag.Int("history-max", historyMaxFromEnv(), "maximum number of releases kept in release history, with 0 meaning no limit")
printVersion = flag.Bool("version", false, "print the version number")
// rootServer is the root gRPC server.
//
@ -100,6 +107,7 @@ var (
)
func main() {
klog.InitFlags(nil)
// TODO: use spf13/cobra for tiller instead of flags
flag.Parse()
@ -141,6 +149,18 @@ func start() {
env.Releases = storage.Init(secrets)
env.Releases.Log = newLogger("storage").Printf
case storageSQL:
sqlDriver, err := driver.NewSQL(
*sqlDialect,
*sqlConnectionString,
newLogger("storage/driver").Printf,
)
if err != nil {
logger.Fatalf("Cannot initialize SQL storage driver: %v", err)
}
env.Releases = storage.Init(sqlDriver)
env.Releases.Log = newLogger("storage").Printf
}
if *maxHistory > 0 {
@ -185,7 +205,7 @@ func start() {
logger.Printf("Starting Tiller %s (tls=%t)", version.GetVersion(), *tlsEnable || *tlsVerify)
logger.Printf("GRPC listening on %s", *grpcAddr)
logger.Printf("Probes listening on %s", probeAddr)
logger.Printf("Probes listening on %s", *probeAddr)
logger.Printf("Storage driver is %s", env.Releases.Name())
logger.Printf("Max history per release is %d", *maxHistory)
@ -211,7 +231,7 @@ func start() {
goprom.Register(rootServer)
addPrometheusHandler(mux)
if err := http.ListenAndServe(probeAddr, mux); err != nil {
if err := http.ListenAndServe(*probeAddr, mux); err != nil {
probeErrCh <- err
}
}()

@ -22,7 +22,7 @@ The directory that contains a chart MUST have the same name as the chart. Thus,
## Version Numbers
Wherever possible, Helm uses [SemVer 2](http://semver.org) to represent version numbers. (Note that Docker image tags do not necessarily follow SemVer, and are thus considered an unfortunate exception to the rule.)
Wherever possible, Helm uses [SemVer 2](https://semver.org) to represent version numbers. (Note that Docker image tags do not necessarily follow SemVer, and are thus considered an unfortunate exception to the rule.)
When SemVer versions are stored in Kubernetes labels, we conventionally alter the `+` character to an `_` character, as labels do not allow the `+` sign as a value.

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

@ -88,7 +88,7 @@ data is lost after one parse.
## Consider How Users Will Use Your Values
There are three potential sources of values:
There are four potential sources of values:
- A chart's `values.yaml` file
- A values file supplied by `helm install -f` or `helm upgrade -f`

@ -123,6 +123,35 @@ startup.
This part shows several ways to serve a chart repository.
### ChartMuseum
The Helm project provides an open-source Helm repository server called [ChartMuseum](https://chartmuseum.com) that you can host yourself.
ChartMuseum supports multiple cloud storage backends. Configure it to point to the directory or bucket containing your chart packages, and the index.yaml file will be generated dynamically.
It can be deployed easily as a [Helm chart](https://github.com/helm/charts/tree/master/stable/chartmuseum):
```
helm install stable/chartmuseum
```
and also as a [Docker image](https://hub.docker.com/r/chartmuseum/chartmuseum/tags):
```
docker run --rm -it \
-p 8080:8080 \
-v $(pwd)/charts:/charts \
-e DEBUG=true \
-e STORAGE=local \
-e STORAGE_LOCAL_ROOTDIR=/charts \
chartmuseum/chartmuseum
```
You can then add the repo to your local repository list:
```
helm repo add chartmuseum http://localhost:8080
```
ChartMuseum provides other features, such as an API for chart uploads. Please see the [README](https://github.com/helm/chartmuseum) for more info.
### Google Cloud Storage
The first step is to **create your GCS bucket**. We'll call ours
@ -153,6 +182,10 @@ Charts repository hosts its charts, so you may want to take a
You can also set up chart repositories using JFrog Artifactory.
Read more about chart repositories with JFrog Artifactory [here](https://www.jfrog.com/confluence/display/RTF/Helm+Chart+Repositories)
### ProGet
Helm chart repositories are supported by ProGet. For more information, visit the [Helm repository documentation](https://inedo.com/support/documentation/proget/feeds/helm) on the Inedo website.
### Github Pages example
In a similar way you can create charts repository using GitHub Pages.

@ -38,7 +38,7 @@ Building synchronization state...
Starting synchronization
Would copy file://fantastic-charts/alpine-0.1.0.tgz to gs://fantastic-charts/alpine-0.1.0.tgz
Would copy file://fantastic-charts/index.yaml to gs://fantastic-charts/index.yaml
Are you sure you would like to continue with these changes?? [y/N]} y
Are you sure you would like to continue with these changes? [y/N]} y
Building synchronization state...
Starting synchronization
Copying file://fantastic-charts/alpine-0.1.0.tgz [Content-Type=application/x-tar]...

@ -54,13 +54,13 @@ metadata:
name: {{ .Release.Name }}-configmap
data:
{{- $files := .Files }}
{{- range tuple "config1.toml" "config2.toml" "config3.toml" }}
{{- range list "config1.toml" "config2.toml" "config3.toml" }}
{{ . }}: |-
{{ $files.Get . }}
{{- end }}
```
This config map uses several of the techniques discussed in previous sections. For example, we create a `$files` variable to hold a reference to the `.Files` object. We also use the `tuple` function to create a list of files that we loop through. Then we print each file name (`{{.}}: |-`) followed by the contents of the file `{{ $files.Get . }}`.
This config map uses several of the techniques discussed in previous sections. For example, we create a `$files` variable to hold a reference to the `.Files` object. We also use the `list` function to create a list of files that we loop through. Then we print each file name (`{{.}}: |-`) followed by the contents of the file `{{ $files.Get . }}`.
Running this template will produce a single ConfigMap with the contents of all three files:
@ -129,6 +129,7 @@ You have multiple options with Globs:
Or
```yaml
{{ $root := . }}
{{ range $path, $bytes := .Files.Glob "foo/*" }}
{{ base $path }}: '{{ $root.Files.Get $path | b64enc }}'
{{ end }}

@ -1,6 +1,6 @@
# Built-in Objects
Objects are passed into a template from the template engine. And your code can pass objects around (we'll see examples when we look at the `with` and `range` statements). There are even a few ways to create new objects within your templates, like with the `tuple` function we'll see later.
Objects are passed into a template from the template engine. And your code can pass objects around (we'll see examples when we look at the `with` and `range` statements). There are even a few ways to create new objects within your templates, like with the `list` function we'll see later.
Objects can be simple, and have just one value. Or they can contain other objects or functions. For example. the `Release` object contains several objects (like `Release.Name`) and the `Files` object has a few functions.
@ -22,7 +22,7 @@ In the previous section, we use `{{.Release.Name}}` to insert the name of a rele
- `Files.GetBytes` is a function for getting the contents of a file as an array of bytes instead of as a string. This is useful for things like images.
- `Capabilities`: This provides information about what capabilities the Kubernetes cluster supports.
- `Capabilities.APIVersions` is a set of versions.
- `Capabilities.APIVersions.Has $version` indicates whether a version (`batch/v1`) is enabled on the cluster.
- `Capabilities.APIVersions.Has $version` indicates whether a version (e.g., `batch/v1`) or resource (e.g., `apps/v1/Deployment`) is available on the cluster. Note, resources were not available before Helm v2.15.
- `Capabilities.KubeVersion` provides a way to look up the Kubernetes version. It has the following values: `Major`, `Minor`, `GitVersion`, `GitCommit`, `GitTreeState`, `BuildDate`, `GoVersion`, `Compiler`, and `Platform`.
- `Capabilities.TillerVersion` provides a way to look up the Tiller version. It has the following values: `SemVer`, `GitCommit`, and `GitTreeState`.
- `Template`: Contains information about the current template that is being executed

@ -20,7 +20,7 @@ The first control structure we'll look at is for conditionally including blocks
The basic structure for a conditional looks like this:
```
```yaml
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
@ -53,7 +53,7 @@ data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }}
{{ if and (.Values.favorite.drink) (eq .Values.favorite.drink "coffee") }}mug: true{{ end }}
{{ if and .Values.favorite.drink (eq .Values.favorite.drink "coffee") }}mug: true{{ end }}
```
Note that `.Values.favorite.drink` must be defined or else it will throw an error when comparing it to "coffee". Since we commented out `drink: coffee` in our last example, the output should not include a `mug: true` flag. But if we add that line back into our `values.yaml` file, the output should look like this:
@ -115,7 +115,7 @@ data:
`mug` is incorrectly indented. Let's simply out-dent that one line, and re-run:
```
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
@ -224,7 +224,7 @@ The next control structure to look at is the `with` action. This controls variab
The syntax for `with` is similar to a simple `if` statement:
```
```yaml
{{ with PIPELINE }}
# restricted scope
{{ end }}
@ -329,15 +329,15 @@ data:
- "Onions"
```
Now, in this example we've done something tricky. The `toppings: |-` line is declaring a multi-line string. So our list of toppings is actually not a YAML list. It's a big string. Why would we do this? Because the data in ConfigMaps `data` is composed of key/value pairs, where both the key and the value are simple strings. To understand why this is the case, take a look at the [Kubernetes ConfigMap docs](http://kubernetes.io/docs/user-guide/configmap/). For us, though, this detail doesn't matter much.
Now, in this example we've done something tricky. The `toppings: |-` line is declaring a multi-line string. So our list of toppings is actually not a YAML list. It's a big string. Why would we do this? Because the data in ConfigMaps `data` is composed of key/value pairs, where both the key and the value are simple strings. To understand why this is the case, take a look at the [Kubernetes ConfigMap docs](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/). For us, though, this detail doesn't matter much.
> The `|-` marker in YAML takes a multi-line string. This can be a useful technique for embedding big blocks of data inside of your manifests, as exemplified here.
Sometimes it's useful to be able to quickly make a list inside of your template, and then iterate over that list. Helm templates have a function to make this easy: `tuple`. In computer science, a tuple is a list-like collection of fixed size, but with arbitrary data types. This roughly conveys the way a `tuple` is used.
Sometimes it's useful to be able to quickly make a list inside of your template, and then iterate over that list. Helm templates have a function that's called just that: `list`.
```yaml
sizes: |-
{{- range tuple "small" "medium" "large" }}
{{- range list "small" "medium" "large" }}
- {{ . }}
{{- end }}
```
@ -351,4 +351,4 @@ The above will produce this:
- large
```
In addition to lists and tuples, `range` can be used to iterate over collections that have a key and a value (like a `map` or `dict`). We'll see how to do that in the next section when we introduce template variables.
In addition to lists, `range` can be used to iterate over collections that have a key and a value (like a `map` or `dict`). We'll see how to do that in the next section when we introduce template variables.

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

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

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

Loading…
Cancel
Save