Resolve conflicts

Signed-off-by: yakkala sarvani mounika <sarvanimounika@gmail.com>
pull/7728/head
yakkala sarvani mounika 5 years ago
commit 084d729c75

@ -5,11 +5,15 @@ jobs:
build: build:
working_directory: ~/helm.sh/helm working_directory: ~/helm.sh/helm
docker: docker:
- image: circleci/golang:1.14 - image: circleci/golang:1.15
auth:
username: $DOCKER_USER
password: $DOCKER_PASS
environment: environment:
GOCACHE: "/tmp/go/cache" GOCACHE: "/tmp/go/cache"
GOLANGCI_LINT_VERSION: "1.27.0" GOLANGCI_LINT_VERSION: "1.36.0"
steps: steps:
- checkout - checkout

@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '29 6 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

@ -6,10 +6,11 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v3 - uses: actions/stale@v3.0.14
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'
exempt-issue-labels: 'keep+open,v4.x' exempt-issue-labels: 'keep open,v4.x'
days-before-stale: 90 days-before-stale: 90
days-before-close: 30 days-before-close: 30
operations-per-run: 100

@ -191,7 +191,9 @@ below.
issue to a milestone until the questions are answered. issue to a milestone until the questions are answered.
- We attempt to do this process at least once per work day. - We attempt to do this process at least once per work day.
3. Discussion 3. Discussion
- issues that are labeled as `feature` or `bug` should be connected to the PR that resolves it. - Issues that are labeled `feature` or `proposal` must write a Helm Improvement Proposal (HIP).
See [Proposing an Idea](#proposing-an-idea). Smaller quality-of-life enhancements are exempt.
- Issues that are labeled as `feature` or `bug` should be connected to the PR that resolves it.
- Whoever is working on a `feature` or `bug` issue (whether a maintainer or someone from the - Whoever is working on a `feature` or `bug` issue (whether a maintainer or someone from the
community), should either assign the issue to themself or make a comment in the issue saying community), should either assign the issue to themself or make a comment in the issue saying
that they are taking it. that they are taking it.
@ -200,9 +202,30 @@ below.
and reduce noise. Should the issue need to stay open, the `keep open` label can be added. and reduce noise. Should the issue need to stay open, the `keep open` label can be added.
4. Issue closure 4. Issue closure
## Proposing an Idea
Before proposing a new idea to the Helm project, please make sure to write up a [Helm Improvement
Proposal](https://github.com/helm/community/tree/master/hips). A Helm Improvement Proposal is a
design document that describes a new feature for the Helm project. The proposal should provide a
concise technical specification and rationale for the feature.
It is also worth considering vetting your idea with the community via the
[cncf-helm](mailto:cncf-helm@lists.cncf.io) mailing list. Vetting an idea publicly before going as
far as writing a proposal is meant to save the potential author time. Many ideas have been proposed;
it's quite likely there are others in the community who may be working on a similar proposal, or a
similar proposal may have already been written.
HIPs are submitted to the [helm/community repository](https://github.com/helm/community). [HIP
1](https://github.com/helm/community/blob/master/hips/hip-0001.md) describes the process to write a
HIP as well as the review process.
After your proposal has been approved, follow the [developer's
guide](https://helm.sh/docs/community/developers/) to get started.
## How to Contribute a Patch ## How to Contribute a Patch
1. Identify or create the related issue. 1. Identify or create the related issue. If you're proposing a larger change to
Helm, see [Proposing an Idea](#proposing-an-idea).
2. Fork the desired repo; develop and test your code changes. 2. Fork the desired repo; develop and test your code changes.
3. Submit a pull request, making sure to sign your work and link the related issue. 3. Submit a pull request, making sure to sign your work and link the related issue.

@ -5,9 +5,12 @@ TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/pp
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum
BINNAME ?= helm BINNAME ?= helm
GOPATH = $(shell go env GOPATH) GOBIN = $(shell go env GOBIN)
GOX = $(GOPATH)/bin/gox ifeq ($(GOBIN),)
GOIMPORTS = $(GOPATH)/bin/goimports GOBIN = $(shell go env GOPATH)/bin
endif
GOX = $(GOBIN)/gox
GOIMPORTS = $(GOBIN)/goimports
ARCH = $(shell uname -p) ARCH = $(shell uname -p)
ACCEPTANCE_DIR:=../acceptance-testing ACCEPTANCE_DIR:=../acceptance-testing
@ -21,7 +24,9 @@ TESTS := .
TESTFLAGS := TESTFLAGS :=
LDFLAGS := -w -s LDFLAGS := -w -s
GOFLAGS := GOFLAGS :=
SRC := $(shell find . -type f -name '*.go' -print)
# Rebuild the buinary if any of these files change
SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum
# Required for globs to work correctly # Required for globs to work correctly
SHELL = /usr/bin/env bash SHELL = /usr/bin/env bash
@ -52,6 +57,16 @@ LDFLAGS += -X helm.sh/helm/v3/internal/version.gitCommit=${GIT_COMMIT}
LDFLAGS += -X helm.sh/helm/v3/internal/version.gitTreeState=${GIT_DIRTY} LDFLAGS += -X helm.sh/helm/v3/internal/version.gitTreeState=${GIT_DIRTY}
LDFLAGS += $(EXT_LDFLAGS) LDFLAGS += $(EXT_LDFLAGS)
# Define constants based on the client-go version
K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s.io/client-go)))
K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1)))
K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER))
LDFLAGS += -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
LDFLAGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
.PHONY: all .PHONY: all
all: build all: build
@ -98,7 +113,7 @@ test-coverage:
.PHONY: test-style .PHONY: test-style
test-style: test-style:
GO111MODULE=on golangci-lint run GO111MODULE=on golangci-lint run --timeout 5m0s
@scripts/validate-license.sh @scripts/validate-license.sh
.PHONY: test-acceptance .PHONY: test-acceptance
@ -124,6 +139,13 @@ coverage:
format: $(GOIMPORTS) format: $(GOIMPORTS)
GO111MODULE=on go list -f '{{.Dir}}' ./... | xargs $(GOIMPORTS) -w -local helm.sh/helm GO111MODULE=on go list -f '{{.Dir}}' ./... | xargs $(GOIMPORTS) -w -local helm.sh/helm
# Generate golden files used in unit tests
.PHONY: gen-test-golden
gen-test-golden:
gen-test-golden: PKG = ./cmd/helm ./pkg/action
gen-test-golden: TESTFLAGS = -update
gen-test-golden: test-unit
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# dependencies # dependencies

@ -9,7 +9,7 @@ Helm is a tool for managing Charts. Charts are packages of pre-configured Kubern
Use Helm to: Use Helm to:
- Find and use [popular software packaged as Helm Charts](https://hub.helm.sh) to run in Kubernetes - Find and use [popular software packaged as Helm Charts](https://artifacthub.io/packages/search?kind=0) to run in Kubernetes
- Share your own applications as Helm Charts - Share your own applications as Helm Charts
- Create reproducible builds of your Kubernetes applications - Create reproducible builds of your Kubernetes applications
- Intelligently manage your Kubernetes manifest files - Intelligently manage your Kubernetes manifest files
@ -66,7 +66,7 @@ You can reach the Helm community and developers via the following channels:
- [#charts](https://kubernetes.slack.com/messages/charts) - [#charts](https://kubernetes.slack.com/messages/charts)
- Mailing List: - Mailing List:
- [Helm Mailing List](https://lists.cncf.io/g/cncf-helm) - [Helm Mailing List](https://lists.cncf.io/g/cncf-helm)
- Developer Call: Thursdays at 9:30-10:00 Pacific. [https://zoom.us/j/696660622](https://zoom.us/j/696660622) - Developer Call: Thursdays at 9:30-10:00 Pacific ([meeting details](https://github.com/helm/community/blob/master/communication.md#meetings))
### Code of conduct ### Code of conduct

@ -27,7 +27,7 @@ import (
) )
const completionDesc = ` const completionDesc = `
Generate autocompletions script for Helm for the specified shell. Generate autocompletion scripts for Helm for the specified shell.
` `
const bashCompDesc = ` const bashCompDesc = `
Generate the autocompletion script for Helm for the bash shell. Generate the autocompletion script for Helm for the bash shell.
@ -74,15 +74,14 @@ var disableCompDescriptions bool
func newCompletionCmd(out io.Writer) *cobra.Command { func newCompletionCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "completion", Use: "completion",
Short: "generate autocompletions script for the specified shell", Short: "generate autocompletion scripts for the specified shell",
Long: completionDesc, Long: completionDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
} }
bash := &cobra.Command{ bash := &cobra.Command{
Use: "bash", Use: "bash",
Short: "generate autocompletions script for bash", Short: "generate autocompletion script for bash",
Long: bashCompDesc, Long: bashCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -94,22 +93,21 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
zsh := &cobra.Command{ zsh := &cobra.Command{
Use: "zsh", Use: "zsh",
Short: "generate autocompletions script for zsh", Short: "generate autocompletion script for zsh",
Long: zshCompDesc, Long: zshCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions, ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionZsh(out, cmd) return runCompletionZsh(out, cmd)
}, },
} }
zsh.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
fish := &cobra.Command{ fish := &cobra.Command{
Use: "fish", Use: "fish",
Short: "generate autocompletions script for fish", Short: "generate autocompletion script for fish",
Long: fishCompDesc, Long: fishCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions, ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionFish(out, cmd) return runCompletionFish(out, cmd)
@ -145,148 +143,29 @@ fi
} }
func runCompletionZsh(out io.Writer, cmd *cobra.Command) error { func runCompletionZsh(out io.Writer, cmd *cobra.Command) error {
zshInitialization := `#compdef helm var err error
if disableCompDescriptions {
__helm_bash_source() { err = cmd.Root().GenZshCompletionNoDesc(out)
alias shopt=':' } else {
alias _expand=_bash_expand err = cmd.Root().GenZshCompletion(out)
alias _complete=_bash_comp
emulate -L sh
setopt kshglob noshglob braceexpand
source "$@"
}
__helm_type() {
# -t is not supported by zsh
if [ "$1" == "-t" ]; then
shift
# fake Bash 4 to disable "complete -o nospace". Instead
# "compopt +-o nospace" is used in the code to toggle trailing
# spaces. We don't support that, but leave trailing spaces on
# all the time
if [ "$1" = "__helm_compopt" ]; then
echo builtin
return 0
fi
fi
type "$@"
} }
__helm_compgen() {
local completions w // In case the user renamed the helm binary (e.g., to be able to run
completions=( $(compgen "$@") ) || return $? // both helm2 and helm3), we hook the new binary name to the completion function
# filter by given word as prefix if binary := filepath.Base(os.Args[0]); binary != "helm" {
while [[ "$1" = -* && "$1" != -- ]]; do renamedBinaryHook := `
shift # Hook the command used to generate the completion script
shift # to the helm completion function to handle the case where
done # the user renamed the helm binary
if [[ "$1" == -- ]]; then compdef _helm %[1]s
shift
fi
for w in "${completions[@]}"; do
if [[ "${w}" = "$1"* ]]; then
# Use printf instead of echo because it is possible that
# the value to print is -n, which would be interpreted
# as a flag to echo
printf "%s\n" "${w}"
fi
done
}
__helm_compopt() {
true # don't do anything. Not supported by bashcompinit in zsh
}
__helm_ltrim_colon_completions()
{
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
# Remove colon-word prefix from COMPREPLY items
local colon_word=${1%${1##*:}}
local i=${#COMPREPLY[*]}
while [[ $((--i)) -ge 0 ]]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
done
fi
}
__helm_get_comp_words_by_ref() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
words=("${COMP_WORDS[@]}")
cword=("${COMP_CWORD[@]}")
}
__helm_filedir() {
local RET OLD_IFS w qw
__debug "_filedir $@ cur=$cur"
if [[ "$1" = \~* ]]; then
# somehow does not work. Maybe, zsh does not call this at all
eval echo "$1"
return 0
fi
OLD_IFS="$IFS"
IFS=$'\n'
if [ "$1" = "-d" ]; then
shift
RET=( $(compgen -d) )
else
RET=( $(compgen -f) )
fi
IFS="$OLD_IFS"
IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
for w in ${RET[@]}; do
if [[ ! "${w}" = "${cur}"* ]]; then
continue
fi
if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
qw="$(__helm_quote "${w}")"
if [ -d "${w}" ]; then
COMPREPLY+=("${qw}/")
else
COMPREPLY+=("${qw}")
fi
fi
done
}
__helm_quote() {
if [[ $1 == \'* || $1 == \"* ]]; then
# Leave out first character
printf %q "${1:1}"
else
printf %q "$1"
fi
}
autoload -U +X bashcompinit && bashcompinit
# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then
LWORD='\<'
RWORD='\>'
fi
__helm_convert_bash_to_zsh() {
sed \
-e 's/declare -F/whence -w/' \
-e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
-e "s/${LWORD}_filedir${RWORD}/__helm_filedir/g" \
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__helm_get_comp_words_by_ref/g" \
-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}/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)) fmt.Fprintf(out, renamedBinaryHook, binary)
}
runCompletionBash(out, cmd) // Cobra doesn't source zsh completion file, explicitly doing it here
fmt.Fprintf(out, "compdef _helm helm")
zshTail := ` return err
BASH_COMPLETION_EOF
}
__helm_bash_source <(__helm_convert_bash_to_zsh)
`
out.Write([]byte(zshTail))
return nil
} }
func runCompletionFish(out io.Writer, cmd *cobra.Command) error { func runCompletionFish(out io.Writer, cmd *cobra.Command) error {

@ -31,7 +31,12 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
storage.Create(&release.Release{ storage.Create(&release.Release{
Name: "myrelease", Name: "myrelease",
Info: &release.Info{Status: release.StatusDeployed}, Info: &release.Info{Status: release.StatusDeployed},
Chart: &chart.Chart{}, Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Myrelease-Chart",
Version: "1.2.3",
},
},
Version: 1, Version: 1,
}) })

@ -82,19 +82,18 @@ the contents of a chart.
This will produce an error if the chart cannot be loaded. This will produce an error if the chart cannot be loaded.
` `
func newDependencyCmd(out io.Writer) *cobra.Command { func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "dependency update|build|list", Use: "dependency update|build|list",
Aliases: []string{"dep", "dependencies"}, Aliases: []string{"dep", "dependencies"},
Short: "manage a chart's dependencies", Short: "manage a chart's dependencies",
Long: dependencyDesc, Long: dependencyDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
} }
cmd.AddCommand(newDependencyListCmd(out)) cmd.AddCommand(newDependencyListCmd(out))
cmd.AddCommand(newDependencyUpdateCmd(out)) cmd.AddCommand(newDependencyUpdateCmd(cfg, out))
cmd.AddCommand(newDependencyBuildCmd(out)) cmd.AddCommand(newDependencyBuildCmd(cfg, out))
return cmd return cmd
} }

@ -41,7 +41,7 @@ If no lock file is found, 'helm dependency build' will mirror the behavior
of 'helm dependency update'. of 'helm dependency update'.
` `
func newDependencyBuildCmd(out io.Writer) *cobra.Command { func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewDependency() client := action.NewDependency()
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -58,7 +58,9 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
Out: out, Out: out,
ChartPath: chartpath, ChartPath: chartpath,
Keyring: client.Keyring, Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient,
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug, Debug: settings.Debug,
@ -77,6 +79,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures") f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys") f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd return cmd
} }

@ -22,6 +22,7 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v3/pkg/repo/repotest"
@ -37,6 +38,27 @@ func TestDependencyBuildCmd(t *testing.T) {
rootDir := srv.Root() rootDir := srv.Root()
srv.LinkIndices() srv.LinkIndices()
ociSrv, err := repotest.NewOCIServer(t, srv.Root())
if err != nil {
t.Fatal(err)
}
ociChartName := "oci-depending-chart"
c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
if err := chartutil.SaveDir(c, ociSrv.Dir); err != nil {
t.Fatal(err)
}
ociSrv.Run(t, repotest.WithDependingChart(c))
err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
if err != nil {
t.Fatal("failed to set environment variable enabling OCI support")
}
dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...)
}
chartname := "depbuild" chartname := "depbuild"
createTestingChart(t, rootDir, chartname, srv.URL()) createTestingChart(t, rootDir, chartname, srv.URL())
repoFile := filepath.Join(rootDir, "repositories.yaml") repoFile := filepath.Join(rootDir, "repositories.yaml")
@ -99,6 +121,35 @@ func TestDependencyBuildCmd(t *testing.T) {
if v := reqver.Version; v != "0.1.0" { if v := reqver.Version; v != "0.1.0" {
t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v) t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v)
} }
skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err = executeActionCommand(skipRefreshCmd)
// In this pass, we check --skip-refresh option becomes effective.
if err != nil {
t.Logf("Output: %s", out)
t.Fatal(err)
}
if strings.Contains(out, `update from the "test" chart repository`) {
t.Errorf("Repo did get updated\n%s", out)
}
// OCI dependencies
cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json",
dir(ociChartName),
dir("repositories.yaml"),
dir(),
dir())
_, out, err = executeActionCommand(cmd)
if err != nil {
t.Logf("Output: %s", out)
t.Fatal(err)
}
expect = dir(ociChartName, "charts/oci-dependent-chart-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil {
t.Fatal(err)
}
} }
func TestDependencyBuildCmdWithHelmV2Hash(t *testing.T) { func TestDependencyBuildCmdWithHelmV2Hash(t *testing.T) {

@ -43,7 +43,7 @@ in the Chart.yaml file, but (b) at the wrong version.
` `
// newDependencyUpdateCmd creates a new dependency update command. // newDependencyUpdateCmd creates a new dependency update command.
func newDependencyUpdateCmd(out io.Writer) *cobra.Command { func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewDependency() client := action.NewDependency()
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -63,6 +63,7 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
Keyring: client.Keyring, Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh, SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient,
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug, Debug: settings.Debug,

@ -40,6 +40,23 @@ func TestDependencyUpdateCmd(t *testing.T) {
defer srv.Stop() defer srv.Stop()
t.Logf("Listening on directory %s", srv.Root()) t.Logf("Listening on directory %s", srv.Root())
ociSrv, err := repotest.NewOCIServer(t, srv.Root())
if err != nil {
t.Fatal(err)
}
ociChartName := "oci-depending-chart"
c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
if err := chartutil.SaveDir(c, ociSrv.Dir); err != nil {
t.Fatal(err)
}
ociSrv.Run(t, repotest.WithDependingChart(c))
err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
if err != nil {
t.Fatal("failed to set environment variable enabling OCI support")
}
if err := srv.LinkIndices(); err != nil { if err := srv.LinkIndices(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -115,6 +132,22 @@ func TestDependencyUpdateCmd(t *testing.T) {
if _, err := os.Stat(unexpected); err == nil { if _, err := os.Stat(unexpected); err == nil {
t.Fatalf("Unexpected %q", unexpected) t.Fatalf("Unexpected %q", unexpected)
} }
// test for OCI charts
cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json",
dir(ociChartName),
dir("repositories.yaml"),
dir(),
dir())
_, out, err = executeActionCommand(cmd)
if err != nil {
t.Logf("Output: %s", out)
t.Fatal(err)
}
expect = dir(ociChartName, "charts/oci-dependent-chart-0.1.0.tgz")
if _, err := os.Stat(expect); err != nil {
t.Fatal(err)
}
} }
func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
@ -193,6 +226,19 @@ func createTestingMetadata(name, baseURL string) *chart.Chart {
} }
} }
func createTestingMetadataForOCI(name, registryURL string) *chart.Chart {
return &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV2,
Name: name,
Version: "1.2.3",
Dependencies: []*chart.Dependency{
{Name: "oci-dependent-chart", Version: "0.1.0", Repository: fmt.Sprintf("oci://%s/u/ocitestuser", registryURL)},
},
},
}
}
// createTestingChart creates a basic chart that depends on reqtest-0.1.0 // createTestingChart creates a basic chart that depends on reqtest-0.1.0
// //
// The baseURL can be used to point to a particular repository server. // The baseURL can be used to point to a particular repository server.

@ -16,8 +16,11 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"path"
"path/filepath" "path/filepath"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -41,6 +44,7 @@ type docsOptions struct {
dest string dest string
docTypeString string docTypeString string
topCmd *cobra.Command topCmd *cobra.Command
generateHeaders bool
} }
func newDocsCmd(out io.Writer) *cobra.Command { func newDocsCmd(out io.Writer) *cobra.Command {
@ -62,6 +66,18 @@ func newDocsCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.dest, "dir", "./", "directory to which documentation is written") f.StringVar(&o.dest, "dir", "./", "directory to which documentation is written")
f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)") f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)")
f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files")
cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{"bash", "man", "markdown"}
var comps []string
for _, t := range types {
if strings.HasPrefix(t, toComplete) {
comps = append(comps, t)
}
}
return comps, cobra.ShellCompDirectiveNoFileComp
})
return cmd return cmd
} }
@ -69,6 +85,18 @@ func newDocsCmd(out io.Writer) *cobra.Command {
func (o *docsOptions) run(out io.Writer) error { func (o *docsOptions) run(out io.Writer) error {
switch o.docTypeString { switch o.docTypeString {
case "markdown", "mdown", "md": case "markdown", "mdown", "md":
if o.generateHeaders {
standardLinks := func(s string) string { return s }
hdrFunc := func(filename string) string {
base := filepath.Base(filename)
name := strings.TrimSuffix(base, path.Ext(base))
title := strings.Title(strings.Replace(name, "_", " ", -1))
return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title)
}
return doc.GenMarkdownTreeCustom(o.topCmd, o.dest, hdrFunc, standardLinks)
}
return doc.GenMarkdownTree(o.topCmd, o.dest) return doc.GenMarkdownTree(o.topCmd, o.dest)
case "man": case "man":
manHdr := &doc.GenManHeader{Title: "HELM", Section: "1"} manHdr := &doc.GenManHeader{Title: "HELM", Section: "1"}

@ -20,6 +20,19 @@ import (
"testing" "testing"
) )
func TestDocsTypeFlagCompletion(t *testing.T) {
tests := []cmdTestCase{{
name: "completion for docs --type",
cmd: "__complete docs --type ''",
golden: "output/docs-type-comp.txt",
}, {
name: "completion for docs --type",
cmd: "__complete docs --type mar",
golden: "output/docs-type-filtered-comp.txt",
}}
runTestCmd(t, tests)
}
func TestDocsFileCompletion(t *testing.T) { func TestDocsFileCompletion(t *testing.T) {
checkFileCompletion(t, "docs", false) checkFileCompletion(t, "docs", false)
} }

@ -21,11 +21,12 @@ import (
"fmt" "fmt"
"log" "log"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/klog" "k8s.io/klog/v2"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/cli/output"
@ -66,11 +67,14 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var formatNames []string var formatNames []string
for _, format := range output.Formats() { for format, desc := range output.FormatsWithDesc() {
if strings.HasPrefix(format, toComplete) { if strings.HasPrefix(format, toComplete) {
formatNames = append(formatNames, format) formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
} }
} }
// Sort the results to get a deterministic order for the tests
sort.Strings(formatNames)
return formatNames, cobra.ShellCompDirectiveNoFileComp return formatNames, cobra.ShellCompDirectiveNoFileComp
}) })
@ -150,7 +154,21 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
for _, details := range indexFile.Entries[chartName] { for _, details := range indexFile.Entries[chartName] {
version := details.Metadata.Version version := details.Metadata.Version
if strings.HasPrefix(version, toComplete) { if strings.HasPrefix(version, toComplete) {
versions = append(versions, version) appVersion := details.Metadata.AppVersion
appVersionDesc := ""
if appVersion != "" {
appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
}
created := details.Created.Format("January 2, 2006")
createdDesc := ""
if created != "" {
createdDesc = fmt.Sprintf("Created: %s ", created)
}
deprecated := ""
if details.Metadata.Deprecated {
deprecated = "(deprecated)"
}
versions = append(versions, fmt.Sprintf("%s\t%s%s%s", version, appVersionDesc, createdDesc, deprecated))
} }
} }
} }

@ -41,7 +41,6 @@ func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download extended information of a named release", Short: "download extended information of a named release",
Long: getHelp, Long: getHelp,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
} }
cmd.AddCommand(newGetAllCmd(cfg, out)) cmd.AddCommand(newGetAllCmd(cfg, out))

@ -62,7 +62,7 @@ func main() {
actionConfig := new(action.Configuration) actionConfig := new(action.Configuration)
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil { if err != nil {
debug("%+v", err) warning("%+v", err)
os.Exit(1) os.Exit(1)
} }

@ -193,7 +193,9 @@ func compListRevisions(toComplete string, cfg *action.Configuration, releaseName
for _, release := range hist { for _, release := range hist {
version := strconv.Itoa(release.Version) version := strconv.Itoa(release.Version)
if strings.HasPrefix(version, toComplete) { if strings.HasPrefix(version, toComplete) {
revisions = append(revisions, version) appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", version, appVersion, chartDesc))
} }
} }
return revisions, cobra.ShellCompDirectiveNoFileComp return revisions, cobra.ShellCompDirectiveNoFileComp

@ -140,6 +140,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)") f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)")
f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")

@ -85,6 +85,12 @@ func TestInstall(t *testing.T) {
cmd: "install apollo testdata/testcharts/empty --wait", cmd: "install apollo testdata/testcharts/empty --wait",
golden: "output/install-with-wait.txt", golden: "output/install-with-wait.txt",
}, },
// Install, with wait-for-jobs
{
name: "install with wait-for-jobs",
cmd: "install apollo testdata/testcharts/empty --wait --wait-for-jobs",
golden: "output/install-with-wait-for-jobs.txt",
},
// Install, using the name-template // Install, using the name-template
{ {
name: "install with name-template", name: "install with name-template",

@ -47,7 +47,7 @@ Only items that match the filter will be returned.
$ helm list --filter 'ara[a-z]+' $ helm list --filter 'ara[a-z]+'
NAME UPDATED CHART NAME UPDATED CHART
maudlin-arachnid Mon May 9 16:07:08 2016 alpine-0.1.0 maudlin-arachnid 2020-06-18 14:17:46.125134977 +0000 UTC alpine-0.1.0
If no results are found, 'helm list' will exit 0, but with no output (or in If no results are found, 'helm list' will exit 0, but with no output (or in
the case of no '-q' flag, only headers). the case of no '-q' flag, only headers).
@ -104,16 +104,17 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return nil return nil
default: default:
return outfmt.Write(out, newReleaseListWriter(results)) return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
} }
} }
return outfmt.Write(out, newReleaseListWriter(results)) return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format") f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
f.StringVar(&client.TimeFormat, "time-format", "", `format time using golang time formatter. Example: --time-format "2006-01-02 15:04:05Z0700"`)
f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date") f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order") f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
f.BoolVarP(&client.All, "all", "a", false, "show all releases without any filter applied") f.BoolVarP(&client.All, "all", "a", false, "show all releases without any filter applied")
@ -125,7 +126,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Pending, "pending", false, "show pending releases") f.BoolVar(&client.Pending, "pending", false, "show pending releases")
f.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces") f.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces")
f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch") f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value") f.IntVar(&client.Offset, "offset", 0, "next release index in the list, used to offset from start value")
f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results") f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.") f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.")
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)
@ -147,7 +148,7 @@ type releaseListWriter struct {
releases []releaseElement releases []releaseElement
} }
func newReleaseListWriter(releases []*release.Release) *releaseListWriter { func newReleaseListWriter(releases []*release.Release, timeFormat string) *releaseListWriter {
// Initialize the array so no results returns an empty array instead of null // Initialize the array so no results returns an empty array instead of null
elements := make([]releaseElement, 0, len(releases)) elements := make([]releaseElement, 0, len(releases))
for _, r := range releases { for _, r := range releases {
@ -159,11 +160,17 @@ func newReleaseListWriter(releases []*release.Release) *releaseListWriter {
Chart: fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version), Chart: fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
AppVersion: r.Chart.Metadata.AppVersion, AppVersion: r.Chart.Metadata.AppVersion,
} }
t := "-" t := "-"
if tspb := r.Info.LastDeployed; !tspb.IsZero() { if tspb := r.Info.LastDeployed; !tspb.IsZero() {
if timeFormat != "" {
t = tspb.Format(timeFormat)
} else {
t = tspb.String() t = tspb.String()
} }
}
element.Updated = t element.Updated = t
elements = append(elements, element) elements = append(elements, element)
} }
return &releaseListWriter{elements} return &releaseListWriter{elements}
@ -196,14 +203,15 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
client.Filter = fmt.Sprintf("^%s", toComplete) client.Filter = fmt.Sprintf("^%s", toComplete)
client.SetStateMask() client.SetStateMask()
results, err := client.Run() releases, err := client.Run()
if err != nil { if err != nil {
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
} }
var choices []string var choices []string
for _, res := range results { for _, rel := range releases {
choices = append(choices, res.Name) choices = append(choices,
fmt.Sprintf("%s\t%s-%s -> %s", rel.Name, rel.Chart.Metadata.Name, rel.Chart.Metadata.Version, rel.Info.Status.String()))
} }
return choices, cobra.ShellCompDirectiveNoFileComp return choices, cobra.ShellCompDirectiveNoFileComp

@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) { func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{} known := []string{}
unknown := []string{} unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--registry-config", "--repository-cache", "--repository-config"} kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config"}
knownArg := func(a string) bool { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {

@ -35,7 +35,6 @@ func newPluginCmd(out io.Writer) *cobra.Command {
Use: "plugin", Use: "plugin",
Short: "install, list, or uninstall Helm plugins", Short: "install, list, or uninstall Helm plugins",
Long: pluginHelp, Long: pluginHelp,
ValidArgsFunction: noCompletions, // Disable file completion
} }
cmd.AddCommand( cmd.AddCommand(
newPluginInstallCmd(out), newPluginInstallCmd(out),

@ -51,14 +51,39 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
return cmd return cmd
} }
// Returns all plugins from plugins, except those with names matching ignoredPluginNames
func filterPlugins(plugins []*plugin.Plugin, ignoredPluginNames []string) []*plugin.Plugin {
// if ignoredPluginNames is nil, just return plugins
if ignoredPluginNames == nil {
return plugins
}
var filteredPlugins []*plugin.Plugin
for _, plugin := range plugins {
found := false
for _, ignoredName := range ignoredPluginNames {
if plugin.Metadata.Name == ignoredName {
found = true
break
}
}
if !found {
filteredPlugins = append(filteredPlugins, plugin)
}
}
return filteredPlugins
}
// Provide dynamic auto-completion for plugin names // Provide dynamic auto-completion for plugin names
func compListPlugins(toComplete string) []string { func compListPlugins(toComplete string, ignoredPluginNames []string) []string {
var pNames []string var pNames []string
plugins, err := plugin.FindPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err == nil { if err == nil && len(plugins) > 0 {
for _, p := range plugins { filteredPlugins := filterPlugins(plugins, ignoredPluginNames)
for _, p := range filteredPlugins {
if strings.HasPrefix(p.Metadata.Name, toComplete) { if strings.HasPrefix(p.Metadata.Name, toComplete) {
pNames = append(pNames, p.Metadata.Name) pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata.Name, p.Metadata.Usage))
} }
} }
} }

@ -305,6 +305,50 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
} }
} }
func TestPluginCmdsCompletion(t *testing.T) {
tests := []cmdTestCase{{
name: "completion for plugin update",
cmd: "__complete plugin update ''",
golden: "output/plugin_list_comp.txt",
rels: []*release.Release{},
}, {
name: "completion for plugin update repetition",
cmd: "__complete plugin update args ''",
golden: "output/plugin_repeat_comp.txt",
rels: []*release.Release{},
}, {
name: "completion for plugin uninstall",
cmd: "__complete plugin uninstall ''",
golden: "output/plugin_list_comp.txt",
rels: []*release.Release{},
}, {
name: "completion for plugin uninstall repetition",
cmd: "__complete plugin uninstall args ''",
golden: "output/plugin_repeat_comp.txt",
rels: []*release.Release{},
}, {
name: "completion for plugin list",
cmd: "__complete plugin list ''",
golden: "output/empty_nofile_comp.txt",
rels: []*release.Release{},
}, {
name: "completion for plugin install no args",
cmd: "__complete plugin install ''",
golden: "output/empty_default_comp.txt",
rels: []*release.Release{},
}, {
name: "completion for plugin install one arg",
cmd: "__complete plugin list /tmp ''",
golden: "output/empty_nofile_comp.txt",
rels: []*release.Release{},
}, {}}
for _, test := range tests {
settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
runTestCmd(t, []cmdTestCase{test})
}
}
func TestPluginFileCompletion(t *testing.T) { func TestPluginFileCompletion(t *testing.T) {
checkFileCompletion(t, "plugin", false) checkFileCompletion(t, "plugin", false)
} }

@ -39,10 +39,7 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
Aliases: []string{"rm", "remove"}, Aliases: []string{"rm", "remove"},
Short: "uninstall one or more Helm plugins", Short: "uninstall one or more Helm plugins",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args) return o.complete(args)

@ -40,10 +40,7 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
Aliases: []string{"up"}, Aliases: []string{"up"},
Short: "update one or more Helm plugins", Short: "update one or more Helm plugins",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args) return o.complete(args)

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -42,8 +43,8 @@ file, and MUST pass the verification process. Failure in any part of this will
result in an error, and the chart will not be saved locally. result in an error, and the chart will not be saved locally.
` `
func newPullCmd(out io.Writer) *cobra.Command { func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPull() client := action.NewPullWithOpts(action.WithConfig(cfg))
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "pull [chart URL | repo/chartname] [...]", Use: "pull [chart URL | repo/chartname] [...]",
@ -64,6 +65,12 @@ func newPullCmd(out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
if strings.HasPrefix(args[0], "oci://") {
if !FeatureGateOCI.IsEnabled() {
return FeatureGateOCI.Error()
}
}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
output, err := client.Run(args[i]) output, err := client.Run(args[i])
if err != nil { if err != nil {

@ -32,6 +32,13 @@ func TestPullCmd(t *testing.T) {
} }
defer srv.Stop() defer srv.Stop()
os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
ociSrv, err := repotest.NewOCIServer(t, srv.Root())
if err != nil {
t.Fatal(err)
}
ociSrv.Run(t)
if err := srv.LinkIndices(); err != nil { if err := srv.LinkIndices(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -139,23 +146,70 @@ func TestPullCmd(t *testing.T) {
failExpect: "Failed to fetch chart version", failExpect: "Failed to fetch chart version",
wantError: true, wantError: true,
}, },
{
name: "Fetch OCI Chart",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0", ociSrv.RegistryURL),
expectFile: "./oci-dependent-chart-0.1.0.tgz",
},
{
name: "Fetch OCI Chart with untar",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar", ociSrv.RegistryURL),
expectFile: "./oci-dependent-chart",
expectDir: true,
},
{
name: "Fetch OCI Chart with untar and untardir",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar --untardir ocitest2", ociSrv.RegistryURL),
expectFile: "./ocitest2",
expectDir: true,
},
{
name: "OCI Fetch untar when dir with same name existed",
args: fmt.Sprintf("oci-test-chart oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar --untardir ocitest2 --untar --untardir ocitest2", ociSrv.RegistryURL),
wantError: true,
wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "ocitest2")),
},
{
name: "Fail fetching non-existent OCI chart",
args: fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing --version 0.1.0", ociSrv.RegistryURL),
failExpect: "Failed to fetch",
wantError: true,
},
{
name: "Fail fetching OCI chart without version specified",
args: fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing", ociSrv.RegistryURL),
wantErrorMsg: "Error: --version flag is explicitly required for OCI registries",
wantError: true,
},
{
name: "Fail fetching OCI chart without version specified",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0", ociSrv.RegistryURL),
wantErrorMsg: "Error: --version flag is explicitly required for OCI registries",
wantError: true,
},
{
name: "Fail fetching OCI chart without version specified",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0 --version 0.1.0", ociSrv.RegistryURL),
wantError: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
outdir := srv.Root() outdir := srv.Root()
cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s ", cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s",
tt.args, tt.args,
outdir, outdir,
filepath.Join(outdir, "repositories.yaml"), filepath.Join(outdir, "repositories.yaml"),
outdir, outdir,
filepath.Join(outdir, "config.json"),
) )
// Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182 // Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182
if tt.existFile != "" { if tt.existFile != "" {
file := filepath.Join(outdir, tt.existFile) file := filepath.Join(outdir, tt.existFile)
_, err := os.Create(file) _, err := os.Create(file)
if err != nil { if err != nil {
t.Fatal("err") t.Fatal(err)
} }
} }
if tt.existDir != "" { if tt.existDir != "" {

@ -19,6 +19,8 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"regexp"
"strings"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -39,6 +41,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
client := action.NewReleaseTesting(cfg) client := action.NewReleaseTesting(cfg)
var outfmt = output.Table var outfmt = output.Table
var outputLogs bool var outputLogs bool
var filter []string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "test [RELEASE]", Use: "test [RELEASE]",
@ -53,6 +56,14 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
notName := regexp.MustCompile(`^!\s?name=`)
for _, f := range filter {
if strings.HasPrefix(f, "name=") {
client.Filters["name"] = append(client.Filters["name"], strings.TrimPrefix(f, "name="))
} else if notName.MatchString(f) {
client.Filters["!name"] = append(client.Filters["!name"], notName.ReplaceAllLiteralString(f, ""))
}
}
rel, runErr := client.Run(args[0]) rel, runErr := client.Run(args[0])
// We only return an error if we weren't even able to get the // We only return an error if we weren't even able to get the
// release, otherwise we keep going so we can print status and logs // release, otherwise we keep going so we can print status and logs
@ -80,6 +91,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
f := cmd.Flags() f := cmd.Flags()
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")
f.StringSliceVar(&filter, "filter", []string{}, "specify tests by attribute (currently \"name\") using attribute=value syntax or '!attribute=value' to exclude a test (can specify multiple or separate values with commas: name=test1,name=test2)")
return cmd return cmd
} }

@ -38,7 +38,6 @@ func newRepoCmd(out io.Writer) *cobra.Command {
Short: "add, list, remove, update, and index chart repositories", Short: "add, list, remove, update, and index chart repositories",
Long: repoHelm, Long: repoHelm,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
} }
cmd.AddCommand(newRepoAddCmd(out)) cmd.AddCommand(newRepoAddCmd(out))

@ -29,7 +29,7 @@ import (
"github.com/gofrs/flock" "github.com/gofrs/flock"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
@ -37,12 +37,19 @@ import (
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
// Repositories that have been permanently deleted and no longer work
var deprecatedRepos = map[string]string{
"//kubernetes-charts.storage.googleapis.com": "https://charts.helm.sh/stable",
"//kubernetes-charts-incubator.storage.googleapis.com": "https://charts.helm.sh/incubator",
}
type repoAddOptions struct { type repoAddOptions struct {
name string name string
url string url string
username string username string
password string password string
forceUpdate bool forceUpdate bool
allowDeprecatedRepos bool
certFile string certFile string
keyFile string keyFile string
@ -83,11 +90,21 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior")
return cmd return cmd
} }
func (o *repoAddOptions) run(out io.Writer) error { func (o *repoAddOptions) run(out io.Writer) error {
// Block deprecated repos
if !o.allowDeprecatedRepos {
for oldURL, newURL := range deprecatedRepos {
if strings.Contains(o.url, oldURL) {
return fmt.Errorf("repo %q is no longer available; try %q instead", o.url, newURL)
}
}
}
// Ensure the file directory exists as it is required for file locking // Ensure the file directory exists as it is required for file locking
err := os.MkdirAll(filepath.Dir(o.repoFile), os.ModePerm) err := os.MkdirAll(filepath.Dir(o.repoFile), os.ModePerm)
if err != nil && !os.IsExist(err) { if err != nil && !os.IsExist(err) {
@ -119,7 +136,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
if o.username != "" && o.password == "" { if o.username != "" && o.password == "" {
fd := int(os.Stdin.Fd()) fd := int(os.Stdin.Fd())
fmt.Fprint(out, "Password: ") fmt.Fprint(out, "Password: ")
password, err := terminal.ReadPassword(fd) password, err := term.ReadPassword(fd)
fmt.Fprintln(out) fmt.Fprintln(out)
if err != nil { if err != nil {
return err return err

@ -63,9 +63,15 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
func (o *repoUpdateOptions) run(out io.Writer) error { func (o *repoUpdateOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.repoFile) f, err := repo.LoadFile(o.repoFile)
if isNotExist(err) || len(f.Repositories) == 0 { switch {
case isNotExist(err):
return errNoRepositories
case err != nil:
return errors.Wrapf(err, "failed loading file: %s", o.repoFile)
case len(f.Repositories) == 0:
return errNoRepositories return errNoRepositories
} }
var repos []*repo.ChartRepository var repos []*repo.ChartRepository
for _, cfg := range f.Repositories { for _, cfg := range f.Repositories {
r, err := repo.NewChartRepository(cfg, getter.All(settings)) r, err := repo.NewChartRepository(cfg, getter.All(settings))

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -53,21 +54,28 @@ func TestUpdateCmd(t *testing.T) {
} }
func TestUpdateCustomCacheCmd(t *testing.T) { func TestUpdateCustomCacheCmd(t *testing.T) {
var out bytes.Buffer
rootDir := ensure.TempDir(t) rootDir := ensure.TempDir(t)
cachePath := filepath.Join(rootDir, "updcustomcache") cachePath := filepath.Join(rootDir, "updcustomcache")
_ = os.Mkdir(cachePath, os.ModePerm) os.Mkdir(cachePath, os.ModePerm)
defer os.RemoveAll(cachePath) defer os.RemoveAll(cachePath)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer ts.Stop()
o := &repoUpdateOptions{ o := &repoUpdateOptions{
update: updateCharts, update: updateCharts,
repoFile: "testdata/repositories.yaml", repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
repoCache: cachePath, repoCache: cachePath,
} }
if err := o.run(&out); err != nil { b := ioutil.Discard
if err := o.run(b); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(filepath.Join(cachePath, "charts-index.yaml")); err != nil { if _, err := os.Stat(filepath.Join(cachePath, "test-index.yaml")); err != nil {
t.Fatalf("error finding created index file in custom cache: %#v", err) t.Fatalf("error finding created index file in custom cache: %v", err)
} }
} }

@ -82,6 +82,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails")
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")

@ -54,6 +54,11 @@ func TestRollbackCmd(t *testing.T) {
cmd: "rollback funny-honey 1 --wait", cmd: "rollback funny-honey 1 --wait",
golden: "output/rollback-wait.txt", golden: "output/rollback-wait.txt",
rels: rels, rels: rels,
}, {
name: "rollback a release with wait-for-jobs",
cmd: "rollback funny-honey 1 --wait --wait-for-jobs",
golden: "output/rollback-wait-for-jobs.txt",
rels: rels,
}, { }, {
name: "rollback a release without revision", name: "rollback a release without revision",
cmd: "rollback funny-honey", cmd: "rollback funny-honey",

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"os"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -30,6 +31,7 @@ import (
"helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/repo"
) )
var globalUsage = `The Kubernetes package manager var globalUsage = `The Kubernetes package manager
@ -60,8 +62,9 @@ Environment variables:
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | | $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | | $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBEASGROUPS | set the Username to impersonate for the operation. | | $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
| $HELM_KUBEASUSER | set the Groups to use for impoersonation using a comma-separated list. | | $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. | | $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | | $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
@ -86,9 +89,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
Short: "The Helm package manager for Kubernetes.", Short: "The Helm package manager for Kubernetes.",
Long: globalUsage, Long: globalUsage,
SilenceUsage: true, SilenceUsage: true,
// This breaks completion for 'helm help <TAB>'
// The Cobra release following 1.0 will fix this
//ValidArgsFunction: noCompletions, // Disable file completion
} }
flags := cmd.PersistentFlags() flags := cmd.PersistentFlags()
@ -131,13 +131,13 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules, loadingRules,
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil { &clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
ctxs := []string{} comps := []string{}
for name := range config.Contexts { for name, context := range config.Contexts {
if strings.HasPrefix(name, toComplete) { if strings.HasPrefix(name, toComplete) {
ctxs = append(ctxs, name) comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
} }
} }
return ctxs, cobra.ShellCompDirectiveNoFileComp return comps, cobra.ShellCompDirectiveNoFileComp
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
@ -153,12 +153,22 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true flags.ParseErrorsWhitelist.UnknownFlags = true
flags.Parse(args) flags.Parse(args)
registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug),
registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil {
return nil, err
}
actionConfig.RegistryClient = registryClient
// Add subcommands // Add subcommands
cmd.AddCommand( cmd.AddCommand(
// chart commands // chart commands
newCreateCmd(out), newCreateCmd(out),
newDependencyCmd(out), newDependencyCmd(actionConfig, out),
newPullCmd(out), newPullCmd(actionConfig, out),
newShowCmd(out), newShowCmd(out),
newLintCmd(out), newLintCmd(out),
newPackageCmd(out), newPackageCmd(out),
@ -188,15 +198,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
) )
// Add *experimental* subcommands // Add *experimental* subcommands
registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug),
registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil {
return nil, err
}
actionConfig.RegistryClient = registryClient
cmd.AddCommand( cmd.AddCommand(
newRegistryCmd(actionConfig, out), newRegistryCmd(actionConfig, out),
newChartCmd(actionConfig, out), newChartCmd(actionConfig, out),
@ -208,5 +209,56 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Check permissions on critical files // Check permissions on critical files
checkPerms() checkPerms()
// Check for expired repositories
checkForExpiredRepos(settings.RepositoryConfig)
return cmd, nil return cmd, nil
} }
func checkForExpiredRepos(repofile string) {
expiredRepos := []struct {
name string
old string
new string
}{
{
name: "stable",
old: "kubernetes-charts.storage.googleapis.com",
new: "https://charts.helm.sh/stable",
},
{
name: "incubator",
old: "kubernetes-charts-incubator.storage.googleapis.com",
new: "https://charts.helm.sh/incubator",
},
}
// parse repo file.
// Ignore the error because it is okay for a repo file to be unparseable at this
// stage. Later checks will trap the error and respond accordingly.
repoFile, err := repo.LoadFile(repofile)
if err != nil {
return
}
for _, exp := range expiredRepos {
r := repoFile.Get(exp.name)
if r == nil {
return
}
if url := r.URL; strings.Contains(url, exp.old) {
fmt.Fprintf(
os.Stderr,
"WARNING: %q is deprecated for %q and will be deleted Nov. 13, 2020.\nWARNING: You should switch to %q via:\nWARNING: helm repo add %q %q --force-update\n",
exp.old,
exp.name,
exp.new,
exp.name,
exp.new,
)
}
}
}

@ -19,7 +19,8 @@ limitations under the License.
package main package main
import ( import (
"bufio" "bytes"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -27,15 +28,27 @@ import (
"testing" "testing"
) )
func TestCheckPerms(t *testing.T) { func checkPermsStderr() (string, error) {
// NOTE(bacongobbler): have to open a new file handler here as the default os.Sterr cannot be read from r, w, err := os.Pipe()
stderr, err := os.Open("/dev/stderr")
if err != nil { if err != nil {
t.Fatalf("could not open /dev/stderr for reading: %s", err) return "", err
} }
defer stderr.Close()
reader := bufio.NewReader(stderr)
stderr := os.Stderr
os.Stderr = w
defer func() {
os.Stderr = stderr
}()
checkPerms()
w.Close()
var text bytes.Buffer
io.Copy(&text, r)
return text.String(), nil
}
func TestCheckPerms(t *testing.T) {
tdir, err := ioutil.TempDir("", "helmtest") tdir, err := ioutil.TempDir("", "helmtest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -51,8 +64,7 @@ func TestCheckPerms(t *testing.T) {
settings.KubeConfig = tfile settings.KubeConfig = tfile
defer func() { settings.KubeConfig = tconfig }() defer func() { settings.KubeConfig = tconfig }()
checkPerms() text, err := checkPermsStderr()
text, err := reader.ReadString('\n')
if err != nil { if err != nil {
t.Fatalf("could not read from stderr: %s", err) t.Fatalf("could not read from stderr: %s", err)
} }
@ -64,8 +76,7 @@ func TestCheckPerms(t *testing.T) {
if err := fh.Chmod(0404); err != nil { if err := fh.Chmod(0404); err != nil {
t.Errorf("Could not change mode on file: %s", err) t.Errorf("Could not change mode on file: %s", err)
} }
checkPerms() text, err = checkPermsStderr()
text, err = reader.ReadString('\n')
if err != nil { if err != nil {
t.Fatalf("could not read from stderr: %s", err) t.Fatalf("could not read from stderr: %s", err)
} }

@ -24,8 +24,8 @@ import (
const searchDesc = ` const searchDesc = `
Search provides the ability to search for Helm charts in the various places Search provides the ability to search for Helm charts in the various places
they can be stored including the Helm Hub and repositories you have added. Use they can be stored including the Artifact Hub and repositories you have added.
search subcommands to search different locations for charts. Use search subcommands to search different locations for charts.
` `
func newSearchCmd(out io.Writer) *cobra.Command { func newSearchCmd(out io.Writer) *cobra.Command {
@ -34,7 +34,6 @@ func newSearchCmd(out io.Writer) *cobra.Command {
Use: "search [keyword]", Use: "search [keyword]",
Short: "search for a keyword in charts", Short: "search for a keyword in charts",
Long: searchDesc, Long: searchDesc,
ValidArgsFunction: noCompletions, // Disable file completion
} }
cmd.AddCommand(newSearchHubCmd(out)) cmd.AddCommand(newSearchHubCmd(out))

@ -30,15 +30,23 @@ import (
) )
const searchHubDesc = ` const searchHubDesc = `
Search the Helm Hub or an instance of Monocular for Helm charts. Search for Helm charts in the Artifact Hub or your own hub instance.
The Helm Hub provides a centralized search for publicly available distributed Artifact Hub is a web-based application that enables finding, installing, and
charts. It is maintained by the Helm project. It can be visited at publishing packages and configurations for CNCF projects, including publicly
https://hub.helm.sh available distributed charts Helm charts. It is a Cloud Native Computing
Foundation sandbox project. You can browse the hub at https://artifacthub.io/
Monocular is a web-based application that enables the search and discovery of
charts from multiple Helm Chart repositories. It is the codebase that powers the The [KEYWORD] argument accepts either a keyword string, or quoted string of rich
Helm Hub. You can find it at https://github.com/helm/monocular query options. For rich query options documentation, see
https://artifacthub.github.io/hub/api/?urls.primaryName=Monocular%20compatible%20search%20API#/Monocular/get_api_chartsvc_v1_charts_search
Previous versions of Helm used an instance of Monocular as the default
'endpoint', so for backwards compatibility Artifact Hub is compatible with the
Monocular search API. Similarly, when setting the 'endpoint' flag, the specified
endpoint must also be implement a Monocular compatible search API endpoint.
Note that when specifying a Monocular instance as the 'endpoint', rich queries
are not supported. For API details, see https://github.com/helm/monocular
` `
type searchHubOptions struct { type searchHubOptions struct {
@ -51,8 +59,8 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
o := &searchHubOptions{} o := &searchHubOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "hub [keyword]", Use: "hub [KEYWORD]",
Short: "search for charts in the Helm Hub or an instance of Monocular", Short: "search for charts in the Artifact Hub or your own hub instance",
Long: searchHubDesc, Long: searchHubDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return o.run(out, args) return o.run(out, args)
@ -60,7 +68,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "monocular instance to query for charts") f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table") f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
bindOutputFlag(cmd, &o.outputFormat) bindOutputFlag(cmd, &o.outputFormat)
@ -98,7 +106,14 @@ type hubSearchWriter struct {
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter { func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter {
var elements []hubChartElement var elements []hubChartElement
for _, r := range results { for _, r := range results {
// Backwards compatibility for Monocular
url := endpoint + "/charts/" + r.ID url := endpoint + "/charts/" + r.ID
// Check for artifactHub compatibility
if r.ArtifactHub.PackageURL != "" {
url = r.ArtifactHub.PackageURL
}
elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description}) elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description})
} }
return &hubSearchWriter{elements, columnWidth} return &hubSearchWriter{elements, columnWidth}

@ -26,7 +26,7 @@ import (
func TestSearchHubCmd(t *testing.T) { func TestSearchHubCmd(t *testing.T) {
// Setup a mock search service // Setup a mock search service
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://kubernetes-charts.storage.googleapis.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://kubernetes-charts.storage.googleapis.com/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))

@ -143,7 +143,7 @@ func (o *searchRepoOptions) setupSearchedVersion() {
} }
func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) { func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) {
if len(o.version) == 0 { if o.version == "" {
return res, nil return res, nil
} }
@ -155,15 +155,18 @@ func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Res
data := res[:0] data := res[:0]
foundNames := map[string]bool{} foundNames := map[string]bool{}
for _, r := range res { for _, r := range res {
if _, found := foundNames[r.Name]; found { // if not returning all versions and already have found a result,
// you're done!
if !o.versions && foundNames[r.Name] {
continue continue
} }
v, err := semver.NewVersion(r.Chart.Version) v, err := semver.NewVersion(r.Chart.Version)
if err != nil || constraint.Check(v) { if err != nil {
data = append(data, r) continue
if !o.versions {
foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches
} }
if constraint.Check(v) {
data = append(data, r)
foundNames[r.Name] = true
} }
} }
@ -184,6 +187,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) {
ind, err := repo.LoadIndexFile(f) ind, err := repo.LoadIndexFile(f)
if err != nil { if err != nil {
warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n) warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n)
warning("%s", err)
continue continue
} }
@ -360,9 +364,6 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
} }
if noSpace { if noSpace {
directive = directive | cobra.ShellCompDirectiveNoSpace directive = directive | cobra.ShellCompDirectiveNoSpace
// The cobra.ShellCompDirective flags do not work for zsh right now.
// We handle it ourselves instead.
completions = compEnforceNoSpace(completions)
} }
if !includeFiles { if !includeFiles {
// If we should not include files in the completions, // If we should not include files in the completions,
@ -371,19 +372,3 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
} }
return completions, directive return completions, directive
} }
// This function prevents the shell from adding a space after
// a completion by adding a second, fake completion.
// It is only needed for zsh, but we cannot tell which shell
// is being used here, so we do the fake completion all the time;
// there are no real downsides to doing this for bash as well.
func compEnforceNoSpace(completions []string) []string {
// To prevent the shell from adding space after the completion,
// we trick it by pretending there is a second, longer match.
// We only do this if there is a single choice for completion.
if len(completions) == 1 {
completions = append(completions, completions[0]+".")
cobra.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions), settings.Debug)
}
return completions
}

@ -138,56 +138,72 @@ func mustParseTime(t string) helmtime.Time {
} }
func TestStatusCompletion(t *testing.T) { func TestStatusCompletion(t *testing.T) {
releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release { rels := []*release.Release{
info.LastDeployed = helmtime.Unix(1452902400, 0).UTC() {
return []*release.Release{{
Name: "athos", Name: "athos",
Namespace: "default", Namespace: "default",
Info: info, Info: &release.Info{
Chart: &chart.Chart{}, Status: release.StatusDeployed,
Hooks: hooks, },
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Athos-chart",
Version: "1.2.3",
},
},
}, { }, {
Name: "porthos", Name: "porthos",
Namespace: "default", Namespace: "default",
Info: info, Info: &release.Info{
Chart: &chart.Chart{}, Status: release.StatusFailed,
Hooks: hooks, },
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Porthos-chart",
Version: "111.222.333",
},
},
}, { }, {
Name: "aramis", Name: "aramis",
Namespace: "default", Namespace: "default",
Info: info, Info: &release.Info{
Chart: &chart.Chart{}, Status: release.StatusUninstalled,
Hooks: hooks, },
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Aramis-chart",
Version: "0.0.0",
},
},
}, { }, {
Name: "dartagnan", Name: "dartagnan",
Namespace: "gascony", Namespace: "gascony",
Info: info, Info: &release.Info{
Chart: &chart.Chart{}, Status: release.StatusUnknown,
Hooks: hooks, },
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Dartagnan-chart",
Version: "1.2.3-prerelease",
},
},
}} }}
}
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "completion for status", name: "completion for status",
cmd: "__complete status a", cmd: "__complete status a",
golden: "output/status-comp.txt", golden: "output/status-comp.txt",
rels: releasesMockWithStatus(&release.Info{ rels: rels,
Status: release.StatusDeployed,
}),
}, { }, {
name: "completion for status with too many arguments", name: "completion for status with too many arguments",
cmd: "__complete status dartagnan ''", cmd: "__complete status dartagnan ''",
golden: "output/status-wrong-args-comp.txt", golden: "output/status-wrong-args-comp.txt",
rels: releasesMockWithStatus(&release.Info{ rels: rels,
Status: release.StatusDeployed,
}),
}, { }, {
name: "completion for status with too many arguments", name: "completion for status with global flag",
cmd: "__complete status --debug a", cmd: "__complete status --debug a",
golden: "output/status-comp.txt", golden: "output/status-comp.txt",
rels: releasesMockWithStatus(&release.Info{ rels: rels,
Status: release.StatusDeployed,
}),
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -27,6 +27,8 @@ import (
"sort" "sort"
"strings" "strings"
"helm.sh/helm/v3/pkg/release"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
@ -47,6 +49,7 @@ faked locally. Additionally, none of the server-side testing of chart validity
func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var validate bool var validate bool
var includeCrds bool var includeCrds bool
var skipTests bool
client := action.NewInstall(cfg) client := action.NewInstall(cfg)
valueOpts := &values.Options{} valueOpts := &values.Options{}
var extraAPIs []string var extraAPIs []string
@ -84,6 +87,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if !client.DisableHooks { if !client.DisableHooks {
fileWritten := make(map[string]bool) fileWritten := make(map[string]bool)
for _, m := range rel.Hooks { for _, m := range rel.Hooks {
if skipTests && isTestHook(m) {
continue
}
if client.OutputDir == "" { if client.OutputDir == "" {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
} else { } else {
@ -163,6 +169,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install") f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install")
f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output") f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output")
f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output")
f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall") f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.") f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
@ -171,6 +178,15 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return cmd return cmd
} }
func isTestHook(h *release.Hook) bool {
for _, e := range h.Events {
if e == release.HookTest {
return true
}
}
return false
}
// The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile) // The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile)
// are coppied from the actions package. This is part of a change to correct a // are coppied from the actions package. This is part of a change to correct a
// bug introduced by #8156. As part of the todo to refactor renderResources // bug introduced by #8156. As part of the todo to refactor renderResources

@ -121,6 +121,11 @@ func TestTemplateCmd(t *testing.T) {
wantError: true, wantError: true,
golden: "output/template-with-invalid-yaml-debug.txt", golden: "output/template-with-invalid-yaml-debug.txt",
}, },
{
name: "template skip-tests",
cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath),
golden: "output/template-skip-tests.txt",
},
} }
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -1,3 +1,3 @@
apiVersion: v1 apiVersion: v1
entries: {} entries: {}
generated: "2020-06-23T10:01:59.2530763-07:00" generated: "2020-09-09T19:50:50.198347916-04:00"

@ -2,8 +2,10 @@ apiVersion: v1
entries: entries:
alpine: alpine:
- name: alpine - name: alpine
url: https://kubernetes-charts.storage.googleapis.com/alpine-0.1.0.tgz url: https://charts.helm.sh/stable/alpine-0.1.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2018-06-27T10:00:18.230700509Z"
deprecated: true
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
@ -13,9 +15,11 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
- name: alpine - name: alpine
url: https://kubernetes-charts.storage.googleapis.com/alpine-0.2.0.tgz url: https://charts.helm.sh/stable/alpine-0.2.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2018-07-09T11:34:37.797864902Z"
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
@ -25,9 +29,11 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
- name: alpine - name: alpine
url: https://kubernetes-charts.storage.googleapis.com/alpine-0.3.0-rc.1.tgz url: https://charts.helm.sh/stable/alpine-0.3.0-rc.1.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2020-11-12T08:44:58.872726222Z"
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
@ -37,10 +43,12 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
mariadb: mariadb:
- name: mariadb - name: mariadb
url: https://kubernetes-charts.storage.googleapis.com/mariadb-0.3.0.tgz url: https://charts.helm.sh/stable/mariadb-0.3.0.tgz
checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56 checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
created: "2018-04-23T08:20:27.160959131Z"
home: https://mariadb.org home: https://mariadb.org
sources: sources:
- https://github.com/bitnami/bitnami-docker-mariadb - https://github.com/bitnami/bitnami-docker-mariadb
@ -55,3 +63,4 @@ entries:
- name: Bitnami - name: Bitnami
email: containers@bitnami.com email: containers@bitnami.com
icon: "" icon: ""
apiVersion: v2

@ -0,0 +1,5 @@
bash
man
markdown
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,3 @@
markdown
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,2 @@
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -0,0 +1,2 @@
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -6,6 +6,7 @@ HELM_DEBUG
HELM_KUBEAPISERVER HELM_KUBEAPISERVER
HELM_KUBEASGROUPS HELM_KUBEASGROUPS
HELM_KUBEASUSER HELM_KUBEASUSER
HELM_KUBECAFILE
HELM_KUBECONTEXT HELM_KUBECONTEXT
HELM_KUBETOKEN HELM_KUBETOKEN
HELM_MAX_HISTORY HELM_MAX_HISTORY

@ -0,0 +1,6 @@
NAME: apollo
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

@ -1,5 +1,5 @@
table json Output result in JSON format
json table Output result in human-readable format
yaml yaml Output result in YAML format
:4 :4
Completion ended with directive: ShellCompDirectiveNoFileComp Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,7 @@
args echo args
echo echo stuff
env env stuff
exitwith exitwith code
fullenv show env vars
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,6 @@
echo echo stuff
env env stuff
exitwith exitwith code
fullenv show env vars
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -1,6 +1,6 @@
8 8 App: 1.0, Chart: foo-0.1.0-beta.1
9 9 App: 1.0, Chart: foo-0.1.0-beta.1
10 10 App: 1.0, Chart: foo-0.1.0-beta.1
11 11 App: 1.0, Chart: foo-0.1.0-beta.1
:4 :4
Completion ended with directive: ShellCompDirectiveNoFileComp Completion ended with directive: ShellCompDirectiveNoFileComp

@ -1,4 +1,4 @@
carabins carabins foo-0.1.0-beta.1 -> superseded
musketeers musketeers foo-0.1.0-beta.1 -> deployed
:4 :4
Completion ended with directive: ShellCompDirectiveNoFileComp Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1 @@
Rollback was a success! Happy Helming!

@ -1,4 +1,4 @@
aramis aramis Aramis-chart-0.0.0 -> uninstalled
athos athos Athos-chart-1.2.3 -> deployed
:4 :4
Completion ended with directive: ShellCompDirectiveNoFileComp Completion ended with directive: ShellCompDirectiveNoFileComp

@ -11,7 +11,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "foobar-YWJj-baz" app.kubernetes.io/instance: "foobar-YWJj-baz"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
@ -82,3 +83,32 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchart app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "foobar-YWJj-baz-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "foobar-YWJj-baz-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "foobar-YWJj-baz-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -11,7 +11,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
@ -82,3 +83,32 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subchart app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "RELEASE-NAME-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "RELEASE-NAME-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -5,7 +5,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml

@ -8,8 +8,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1 kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP

@ -8,8 +8,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1 kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP

@ -0,0 +1,86 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
kube-api-version/test: v1
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart

@ -11,7 +11,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
@ -82,3 +83,32 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subchart app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "RELEASE-NAME-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "RELEASE-NAME-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -11,7 +11,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1 kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP
@ -83,3 +84,32 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchart app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "RELEASE-NAME-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "RELEASE-NAME-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -3,13 +3,14 @@
apiVersion: apiextensions.k8s.io/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: testCRDs name: testcrds.testcrdgroups.example.com
spec: spec:
group: testCRDGroups group: testcrdgroups.example.com
version: v1alpha1
names: names:
kind: TestCRD kind: TestCRD
listKind: TestCRDList listKind: TestCRDList
plural: TestCRDs plural: testcrds
shortNames: shortNames:
- tc - tc
singular: authconfig singular: authconfig
@ -27,7 +28,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
@ -87,8 +89,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1 kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP
@ -99,3 +101,32 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchart app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "RELEASE-NAME-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "RELEASE-NAME-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -11,7 +11,8 @@ kind: Role
metadata: metadata:
name: subchart-role name: subchart-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "18" kube-version/minor: "20"
kube-version/version: "v1.18.0" kube-version/version: "v1.20.0"
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
@ -82,3 +83,32 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchart app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "RELEASE-NAME-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "RELEASE-NAME-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -0,0 +1,7 @@
Release "crazy-bunny" has been upgraded. Happy Helming!
NAME: crazy-bunny
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None

@ -1 +1 @@
version.BuildInfo{Version:"v3.3", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.5", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
version.BuildInfo{Version:"v3.3", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.5", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1,5 +1,5 @@
0.3.0-rc.1 0.3.0-rc.1 App: 3.0.0, Created: November 12, 2020
0.2.0 0.2.0 App: 2.3.4, Created: July 9, 2018
0.1.0 0.1.0 App: 1.2.3, Created: June 27, 2018 (deprecated)
:4 :4
Completion ended with directive: ShellCompDirectiveNoFileComp Completion ended with directive: ShellCompDirectiveNoFileComp

@ -1 +1 @@
Version: v3.3 Version: v3.5

@ -1 +1 @@
version.BuildInfo{Version:"v3.3", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.5", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
repositories: repositories:
- name: charts - name: charts
url: "https://kubernetes-charts.storage.googleapis.com" url: "https://charts.helm.sh/stable"

@ -17,5 +17,5 @@ type: application
version: 0.1.0 version: 0.1.0
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. # incremented each time you make changes to the application and it is recommended to use it with quotes.
appVersion: 1.16.0 appVersion: "1.16.0"

@ -1,13 +1,14 @@
apiVersion: apiextensions.k8s.io/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: testCRDs name: testcrds.testcrdgroups.example.com
spec: spec:
group: testCRDGroups group: testcrdgroups.example.com
version: v1alpha1
names: names:
kind: TestCRD kind: TestCRD
listKind: TestCRDList listKind: TestCRDList
plural: TestCRDs plural: testcrds
shortNames: shortNames:
- tc - tc
singular: authconfig singular: authconfig

@ -3,5 +3,6 @@ kind: Role
metadata: metadata:
name: {{ .Chart.Name }}-role name: {{ .Chart.Name }}-role
rules: rules:
- resources: ["*"] - apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]

@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Release.Name }}-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World

@ -0,0 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "{{ .Release.Name }}-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -103,6 +103,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.SkipCRDs = client.SkipCRDs instClient.SkipCRDs = client.SkipCRDs
instClient.Timeout = client.Timeout instClient.Timeout = client.Timeout
instClient.Wait = client.Wait instClient.Wait = client.Wait
instClient.WaitForJobs = client.WaitForJobs
instClient.Devel = client.Devel instClient.Devel = client.Devel
instClient.Namespace = client.Namespace instClient.Namespace = client.Namespace
instClient.Atomic = client.Atomic instClient.Atomic = client.Atomic
@ -179,6 +180,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&client.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(&client.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(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used")
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")

@ -131,6 +131,12 @@ func TestUpgradeCmd(t *testing.T) {
golden: "output/upgrade-with-wait.txt", golden: "output/upgrade-with-wait.txt",
rels: []*release.Release{relMock("crazy-bunny", 2, ch2)}, rels: []*release.Release{relMock("crazy-bunny", 2, ch2)},
}, },
{
name: "upgrade a release with wait-for-jobs",
cmd: fmt.Sprintf("upgrade crazy-bunny --wait --wait-for-jobs '%s'", chartPath),
golden: "output/upgrade-with-wait-for-jobs.txt",
rels: []*release.Release{relMock("crazy-bunny", 2, ch2)},
},
{ {
name: "upgrade a release with missing dependencies", name: "upgrade a release with missing dependencies",
cmd: fmt.Sprintf("upgrade bonkers-bunny %s", missingDepsPath), cmd: fmt.Sprintf("upgrade bonkers-bunny %s", missingDepsPath),

@ -1,19 +1,19 @@
module helm.sh/helm/v3 module helm.sh/helm/v3
go 1.14 go 1.15
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/DATA-DOG/go-sqlmock v1.4.1 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/goutils v1.1.0 github.com/Masterminds/goutils v1.1.1
github.com/Masterminds/semver/v3 v3.1.0 github.com/Masterminds/semver/v3 v3.1.1
github.com/Masterminds/sprig/v3 v3.1.0 github.com/Masterminds/sprig/v3 v3.2.2
github.com/Masterminds/squirrel v1.4.0 github.com/Masterminds/squirrel v1.5.0
github.com/Masterminds/vcs v1.13.1 github.com/Masterminds/vcs v1.13.1
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/containerd/containerd v1.3.4 github.com/containerd/containerd v1.4.3
github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin v0.2.2
github.com/deislabs/oras v0.8.1 github.com/deislabs/oras v0.10.0
github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce
github.com/docker/go-units v0.4.0 github.com/docker/go-units v0.4.0
@ -21,32 +21,34 @@ require (
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.0 github.com/gofrs/flock v0.8.0
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/jessevdk/go-flags v1.4.0 // indirect
github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.8.0 github.com/lib/pq v1.9.0
github.com/mattn/go-shellwords v1.0.10 github.com/mattn/go-shellwords v1.0.11
github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/copystructure v1.1.1
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/image-spec v1.0.1
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.7.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
k8s.io/api v0.19.2 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221
k8s.io/apiextensions-apiserver v0.19.2 k8s.io/api v0.20.2
k8s.io/apimachinery v0.19.2 k8s.io/apiextensions-apiserver v0.20.2
k8s.io/cli-runtime v0.19.2 k8s.io/apimachinery v0.20.2
k8s.io/client-go v0.19.2 k8s.io/apiserver v0.20.2
k8s.io/klog v1.0.0 k8s.io/cli-runtime v0.20.2
k8s.io/kubectl v0.19.2 k8s.io/client-go v0.20.2
k8s.io/klog/v2 v2.5.0
k8s.io/kubectl v0.20.2
sigs.k8s.io/yaml v1.2.0 sigs.k8s.io/yaml v1.2.0
) )
replace ( replace (
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible
github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
github.com/docker/docker => github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
) )

606
go.sum

File diff suppressed because it is too large Load Diff

@ -17,6 +17,7 @@ limitations under the License.
package registry // import "helm.sh/helm/v3/internal/experimental/registry" package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io" "io"
@ -25,6 +26,7 @@ import (
"sort" "sort"
auth "github.com/deislabs/oras/pkg/auth/docker" auth "github.com/deislabs/oras/pkg/auth/docker"
"github.com/deislabs/oras/pkg/content"
"github.com/deislabs/oras/pkg/oras" "github.com/deislabs/oras/pkg/oras"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -144,7 +146,60 @@ func (c *Client) PushChart(ref *Reference) error {
} }
// PullChart downloads a chart from a registry // PullChart downloads a chart from a registry
func (c *Client) PullChart(ref *Reference) error { func (c *Client) PullChart(ref *Reference) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
if ref.Tag == "" {
return buf, errors.New("tag explicitly required")
}
fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo)
store := content.NewMemoryStore()
fullname := ref.FullName()
_ = fullname
_, layerDescriptors, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), store,
oras.WithPullEmptyNameAllowed(),
oras.WithAllowedMediaTypes(KnownMediaTypes()))
if err != nil {
return buf, err
}
numLayers := len(layerDescriptors)
if numLayers < 1 {
return buf, errors.New(
fmt.Sprintf("manifest does not contain at least 1 layer (total: %d)", numLayers))
}
var contentLayer *ocispec.Descriptor
for _, layer := range layerDescriptors {
layer := layer
switch layer.MediaType {
case HelmChartContentLayerMediaType:
contentLayer = &layer
}
}
if contentLayer == nil {
return buf, errors.New(
fmt.Sprintf("manifest does not contain a layer with mediatype %s",
HelmChartContentLayerMediaType))
}
_, b, ok := store.Get(*contentLayer)
if !ok {
return buf, errors.Errorf("Unable to retrieve blob with digest %s", contentLayer.Digest)
}
buf = bytes.NewBuffer(b)
return buf, nil
}
// PullChartToCache pulls a chart from an OCI Registry to the Registry Cache.
// This function is needed for `helm chart pull`, which is experimental and will be deprecated soon.
// Likewise, the Registry cache will soon be deprecated as will this function.
func (c *Client) PullChartToCache(ref *Reference) error {
if ref.Tag == "" { if ref.Tag == "" {
return errors.New("tag explicitly required") return errors.New("tag explicitly required")
} }

@ -22,7 +22,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -33,12 +32,12 @@ import (
"time" "time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
auth "github.com/deislabs/oras/pkg/auth/docker" auth "github.com/deislabs/oras/pkg/auth/docker"
"github.com/docker/distribution/configuration" "github.com/docker/distribution/configuration"
"github.com/docker/distribution/registry" "github.com/docker/distribution/registry"
_ "github.com/docker/distribution/registry/auth/htpasswd" _ "github.com/docker/distribution/registry/auth/htpasswd"
_ "github.com/docker/distribution/registry/storage/driver/inmemory" _ "github.com/docker/distribution/registry/storage/driver/inmemory"
"github.com/phayes/freeport"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -107,7 +106,7 @@ func (suite *RegistryClientTestSuite) SetupSuite() {
// Registry config // Registry config
config := &configuration.Configuration{} config := &configuration.Configuration{}
port, err := getFreePort() port, err := freeport.GetFreePort()
suite.Nil(err, "no error finding free port for test registry") suite.Nil(err, "no error finding free port for test registry")
suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port) suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port)
config.HTTP.Addr = fmt.Sprintf(":%d", port) config.HTTP.Addr = fmt.Sprintf(":%d", port)
@ -202,13 +201,13 @@ func (suite *RegistryClientTestSuite) Test_4_PullChart() {
// non-existent ref // non-existent ref
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
suite.Nil(err) suite.Nil(err)
err = suite.RegistryClient.PullChart(ref) _, err = suite.RegistryClient.PullChart(ref)
suite.NotNil(err) suite.NotNil(err)
// existing ref // existing ref
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)) ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
suite.Nil(err) suite.Nil(err)
err = suite.RegistryClient.PullChart(ref) _, err = suite.RegistryClient.PullChart(ref)
suite.Nil(err) suite.Nil(err)
} }
@ -245,7 +244,7 @@ func (suite *RegistryClientTestSuite) Test_8_ManInTheMiddle() {
suite.Nil(err) suite.Nil(err)
// returns content that does not match the expected digest // returns content that does not match the expected digest
err = suite.RegistryClient.PullChart(ref) _, err = suite.RegistryClient.PullChart(ref)
suite.NotNil(err) suite.NotNil(err)
suite.True(errdefs.IsFailedPrecondition(err)) suite.True(errdefs.IsFailedPrecondition(err))
} }
@ -254,21 +253,6 @@ func TestRegistryClientTestSuite(t *testing.T) {
suite.Run(t, new(RegistryClientTestSuite)) suite.Run(t, new(RegistryClientTestSuite))
} }
// borrowed from https://github.com/phayes/freeport
func getFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
func initCompromisedRegistryTestServer() string { func initCompromisedRegistryTestServer() string {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "manifests") { if strings.Contains(r.URL.Path, "manifests") {

@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Package monocular contains the logic for interacting with monocular instances // Package monocular contains the logic for interacting with a Monocular
// like the Helm Hub. // compatible search API endpoint. For example, as implemented by the Artifact
// Hub.
// //
// This is a library for interacting with monocular // This is a library for interacting with a monocular compatible search API
package monocular package monocular

@ -40,12 +40,18 @@ const SearchPath = "api/chartsvc/v1/charts/search"
// SearchResult represents an individual chart result // SearchResult represents an individual chart result
type SearchResult struct { type SearchResult struct {
ID string `json:"id"` ID string `json:"id"`
ArtifactHub ArtifactHub `json:"artifactHub"`
Type string `json:"type"` Type string `json:"type"`
Attributes Chart `json:"attributes"` Attributes Chart `json:"attributes"`
Links Links `json:"links"` Links Links `json:"links"`
Relationships Relationships `json:"relationships"` Relationships Relationships `json:"relationships"`
} }
// ArtifactHub represents data specific to Artifact Hub instances
type ArtifactHub struct {
PackageURL string `json:"packageUrl"`
}
// Chart is the attributes for the chart // Chart is the attributes for the chart
type Chart struct { type Chart struct {
Name string `json:"name"` Name string `json:"name"`

@ -24,7 +24,7 @@ import (
) )
// A search response for phpmyadmin containing 2 results // A search response for phpmyadmin containing 2 results
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://kubernetes-charts.storage.googleapis.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://kubernetes-charts.storage.googleapis.com/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
func TestSearch(t *testing.T) { func TestSearch(t *testing.T) {

@ -28,11 +28,14 @@ import (
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
// Resolver resolves dependencies from semantic version ranges to a particular version. // Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct { type Resolver struct {
chartpath string chartpath string
@ -88,6 +91,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
} }
continue continue
} }
constraint, err := semver.NewConstraint(d.Version) constraint, err := semver.NewConstraint(d.Version)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
@ -104,21 +108,34 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
continue continue
} }
var vs repo.ChartVersions
var version string
var ok bool
found := true
if !strings.HasPrefix(d.Repository, "oci://") {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName) return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
} }
vs, ok := repoIndex.Entries[d.Name] vs, ok = repoIndex.Entries[d.Name]
if !ok { if !ok {
return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository) return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
} }
found = false
} else {
version = d.Version
if !FeatureGateOCI.IsEnabled() {
return nil, errors.Wrapf(FeatureGateOCI.Error(),
"repository %s is an OCI registry", d.Repository)
}
}
locked[i] = &chart.Dependency{ locked[i] = &chart.Dependency{
Name: d.Name, Name: d.Name,
Repository: d.Repository, Repository: d.Repository,
Version: version,
} }
found := false
// The version are already sorted and hence the first one to satisfy the constraint is used // The version are already sorted and hence the first one to satisfy the constraint is used
for _, ver := range vs { for _, ver := range vs {
v, err := semver.NewVersion(ver.Version) v, err := semver.NewVersion(ver.Version)

@ -3,7 +3,7 @@ entries:
alpine: alpine:
- name: alpine - name: alpine
urls: urls:
- https://kubernetes-charts.storage.googleapis.com/alpine-0.1.0.tgz - https://charts.helm.sh/stable/alpine-0.1.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
@ -13,9 +13,10 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
- name: alpine - name: alpine
urls: urls:
- https://kubernetes-charts.storage.googleapis.com/alpine-0.2.0.tgz - https://charts.helm.sh/stable/alpine-0.2.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
@ -25,10 +26,11 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
mariadb: mariadb:
- name: mariadb - name: mariadb
urls: urls:
- https://kubernetes-charts.storage.googleapis.com/mariadb-0.3.0.tgz - https://charts.helm.sh/stable/mariadb-0.3.0.tgz
checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56 checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
home: https://mariadb.org home: https://mariadb.org
sources: sources:
@ -44,3 +46,4 @@ entries:
- name: Bitnami - name: Bitnami
email: containers@bitnami.com email: containers@bitnami.com
icon: "" icon: ""
apiVersion: v2

@ -77,6 +77,7 @@ func path(filename string) string {
} }
func compare(actual []byte, filename string) error { func compare(actual []byte, filename string) error {
actual = normalize(actual)
if err := update(filename, actual); err != nil { if err := update(filename, actual); err != nil {
return err return err
} }
@ -85,6 +86,7 @@ func compare(actual []byte, filename string) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to read testdata %s", filename) return errors.Wrapf(err, "unable to read testdata %s", filename)
} }
expected = normalize(expected)
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'\n", filename, expected, actual) return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'\n", filename, expected, actual)
} }

@ -29,7 +29,7 @@ var (
// //
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
version = "v3.3" version = "v3.5"
// metadata is extra build time data // metadata is extra build time data
metadata = "" metadata = ""

@ -40,5 +40,5 @@ func (a *ChartPull) Run(out io.Writer, ref string) error {
if err != nil { if err != nil {
return err return err
} }
return a.cfg.RegistryClient.PullChart(r) return a.cfg.RegistryClient.PullChartToCache(r)
} }

@ -26,6 +26,9 @@ import (
// History is the action for checking the release's ledger. // History is the action for checking the release's ledger.
// //
// It provides the implementation of 'helm history'. // It provides the implementation of 'helm history'.
// It returns all the revisions for a specific release.
// To list up to one revision of every release in one specific, or in all,
// namespaces, see the List action.
type History struct { type History struct {
cfg *Configuration cfg *Configuration

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

Loading…
Cancel
Save