update branch with version 3.5.2

pull/8841/head
Matheus Hunsche 5 years ago
commit 48dc5d0117

@ -5,7 +5,11 @@ 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"

@ -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

@ -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
@ -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.
@ -73,16 +73,15 @@ 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,7 +93,7 @@ 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, DisableFlagsInUseLine: true,
@ -103,10 +102,11 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
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, DisableFlagsInUseLine: true,
@ -145,148 +145,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 // In case the user renamed the helm binary (e.g., to be able to run
source "$@" // both helm2 and helm3), we hook the new binary name to the completion function
} if binary := filepath.Base(os.Args[0]); binary != "helm" {
__helm_type() { renamedBinaryHook := `
# -t is not supported by zsh # Hook the command used to generate the completion script
if [ "$1" == "-t" ]; then # to the helm completion function to handle the case where
shift # the user renamed the helm binary
# fake Bash 4 to disable "complete -o nospace". Instead compdef _helm %[1]s
# "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
completions=( $(compgen "$@") ) || return $?
# filter by given word as prefix
while [[ "$1" = -* && "$1" != -- ]]; do
shift
shift
done
if [[ "$1" == -- ]]; then
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 {

@ -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"
@ -38,9 +41,10 @@ It can also generate bash autocompletions.
` `
type docsOptions struct { 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,7 @@ 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")
return cmd return cmd
} }
@ -69,6 +74,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"}

@ -25,7 +25,7 @@ import (
"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/files" "helm.sh/helm/v3/pkg/cli/files"

@ -37,11 +37,10 @@ get extended information about the release, including:
func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "get", Use: "get",
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)
} }

@ -144,6 +144,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")

@ -103,6 +103,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",

@ -46,8 +46,8 @@ regular expressions (Perl compatible) that are applied to the list of releases.
Only items that match the filter will be returned. 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. Example: --time-format 2009-11-17 20:34:10 +0000 UTC")
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")
@ -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() {
t = tspb.String() if timeFormat != "" {
t = tspb.Format(timeFormat)
} else {
t = tspb.String()
}
} }
element.Updated = t element.Updated = t
elements = append(elements, element) elements = append(elements, element)
} }
return &releaseListWriter{elements} return &releaseListWriter{elements}

@ -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+"=") {

@ -32,10 +32,9 @@ Manage client-side Helm plugins.
func newPluginCmd(out io.Writer) *cobra.Command { func newPluginCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &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),

@ -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
} }

@ -34,11 +34,10 @@ It can be used to add, remove, list, and index chart repositories.
func newRepoCmd(out io.Writer) *cobra.Command { func newRepoCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "repo add|remove|list|index|update [ARGS]", Use: "repo add|remove|list|index|update [ARGS]",
Short: "add, list, remove, update, and index chart repositories", Short: "add, list, remove, update, and index chart repositories",
Long: repoHelm, 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,12 +90,22 @@ 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 {
//Ensure the file directory exists as it is required for file locking // 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
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) {
return err return 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()
@ -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,17 +24,16 @@ 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 {
cmd := &cobra.Command{ cmd := &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 {
continue
}
if constraint.Check(v) {
data = append(data, r) data = append(data, r)
if !o.versions { foundNames[r.Name] = true
foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches
}
} }
} }
@ -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
}

@ -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",
},
{ {
name: "chart with template with external file", name: "chart with template with external file",
cmd: fmt.Sprintf("template '%s' --set external=external.txt --include-file external.txt=testdata/files/external.txt", "testdata/testcharts/external"), cmd: fmt.Sprintf("template '%s' --set external=external.txt --include-file external.txt=testdata/files/external.txt", "testdata/testcharts/external"),

@ -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,7 +2,7 @@ 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
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
@ -13,8 +13,9 @@ 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
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
@ -25,8 +26,9 @@ 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
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
@ -37,9 +39,10 @@ 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
home: https://mariadb.org home: https://mariadb.org
sources: sources:
@ -55,3 +58,4 @@ entries:
- name: Bitnami - name: Bitnami
email: containers@bitnami.com email: containers@bitnami.com
icon: "" icon: ""
apiVersion: v2

@ -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

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

@ -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 +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
@ -185,6 +186,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.5.0 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.0.0
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.7.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.4.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
) )

544
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
} }
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) var vs repo.ChartVersions
if err != nil { var version string
return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName) var ok bool
} found := true
if !strings.HasPrefix(d.Repository, "oci://") {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
if err != nil {
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

@ -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

@ -78,6 +78,7 @@ type Install struct {
DisableHooks bool DisableHooks bool
Replace bool Replace bool
Wait bool Wait bool
WaitForJobs bool
Devel bool Devel bool
DependencyUpdate bool DependencyUpdate bool
Timeout time.Duration Timeout time.Duration
@ -347,10 +348,15 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
} }
if i.Wait { if i.Wait {
if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil { if i.WaitForJobs {
return i.failRelease(rel, err) if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil {
return i.failRelease(rel, err)
}
} else {
if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil {
return i.failRelease(rel, err)
}
} }
} }
if !i.DisableHooks { if !i.DisableHooks {

@ -362,6 +362,23 @@ func TestInstallRelease_Wait(t *testing.T) {
is.Equal(res.Info.Status, release.StatusFailed) is.Equal(res.Info.Status, release.StatusFailed)
} }
func TestInstallRelease_WaitForJobs(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "come-fail-away"
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Wait = true
instAction.WaitForJobs = true
vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
}
func TestInstallRelease_Atomic(t *testing.T) { func TestInstallRelease_Atomic(t *testing.T) {
is := assert.New(t) is := assert.New(t)
@ -425,9 +442,9 @@ func TestNameTemplate(t *testing.T) {
}, },
// No such function // No such function
{ {
tpl: "foobar-{{randInt}}", tpl: "foobar-{{randInteger}}",
expected: "", expected: "",
expectedErrorStr: "function \"randInt\" not defined", expectedErrorStr: "function \"randInteger\" not defined",
}, },
// Invalid template // Invalid template
{ {

@ -98,6 +98,9 @@ const (
// List is the action for listing releases. // List is the action for listing releases.
// //
// It provides, for example, the implementation of 'helm list'. // It provides, for example, the implementation of 'helm list'.
// It returns no more than one revision of every release in one specific, or in
// all, namespaces.
// To list all the revisions of a specific release, see the History action.
type List struct { type List struct {
cfg *Configuration cfg *Configuration
@ -122,6 +125,7 @@ type List struct {
// Filter is a filter that is applied to the results // Filter is a filter that is applied to the results
Filter string Filter string
Short bool Short bool
TimeFormat string
Uninstalled bool Uninstalled bool
Superseded bool Superseded bool
Uninstalling bool Uninstalling bool

@ -25,9 +25,8 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
"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/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/provenance"
@ -64,9 +63,11 @@ func (p *Package) Run(path string, vals map[string]interface{}) (string, error)
// If version is set, modify the version. // If version is set, modify the version.
if p.Version != "" { if p.Version != "" {
if err := setVersion(ch, p.Version); err != nil { ch.Metadata.Version = p.Version
return "", err }
}
if err := validateVersion(ch.Metadata.Version); err != nil {
return "", err
} }
if p.AppVersion != "" { if p.AppVersion != "" {
@ -103,14 +104,11 @@ func (p *Package) Run(path string, vals map[string]interface{}) (string, error)
return name, err return name, err
} }
func setVersion(ch *chart.Chart, ver string) error { // validateVersion Verify that version is a Version, and error out if it is not.
// Verify that version is a Version, and error out if it is not. func validateVersion(ver string) error {
if _, err := semver.NewVersion(ver); err != nil { if _, err := semver.NewVersion(ver); err != nil {
return err return err
} }
// Set the version field on the chart.
ch.Metadata.Version = ver
return nil return nil
} }
@ -147,7 +145,7 @@ func promptUser(name string) ([]byte, error) {
fmt.Printf("Password for key %q > ", name) fmt.Printf("Password for key %q > ", name)
// syscall.Stdin is not an int in all environments and needs to be coerced // syscall.Stdin is not an int in all environments and needs to be coerced
// into one there (e.g., Windows) // into one there (e.g., Windows)
pw, err := terminal.ReadPassword(int(syscall.Stdin)) pw, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println() fmt.Println()
return pw, err return pw, err
} }

@ -22,31 +22,11 @@ import (
"path" "path"
"testing" "testing"
"github.com/Masterminds/semver/v3"
"helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart"
) )
func TestSetVersion(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "prow",
Version: "0.0.1",
},
}
expect := "1.2.3-beta.5"
if err := setVersion(c, expect); err != nil {
t.Fatal(err)
}
if c.Metadata.Version != expect {
t.Errorf("Expected %q, got %q", expect, c.Metadata.Version)
}
if err := setVersion(c, "monkeyface"); err == nil {
t.Error("Expected bogus version to return an error.")
}
}
func TestPassphraseFileFetcher(t *testing.T) { func TestPassphraseFileFetcher(t *testing.T) {
secret := "secret" secret := "secret"
directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) directory := ensure.TempFile(t, "passphrase-file", []byte(secret))
@ -100,3 +80,46 @@ func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
t.Error("Expected passphraseFileFetcher returning an error") t.Error("Expected passphraseFileFetcher returning an error")
} }
} }
func TestValidateVersion(t *testing.T) {
type args struct {
ver string
}
tests := []struct {
name string
args args
wantErr error
}{
{
"normal semver version",
args{
ver: "1.1.3-23658",
},
nil,
},
{
"Pre version number starting with 0",
args{
ver: "1.1.3-023658",
},
semver.ErrSegmentStartsZero,
},
{
"Invalid version number",
args{
ver: "1.1.3.sd.023658",
},
semver.ErrInvalidSemVer,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateVersion(tt.args.ver); err != nil {
if err != tt.wantErr {
t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err)
}
}
})
}
}

@ -45,11 +45,30 @@ type Pull struct {
VerifyLater bool VerifyLater bool
UntarDir string UntarDir string
DestDir string DestDir string
cfg *Configuration
} }
// NewPull creates a new Pull object with the given configuration. type PullOpt func(*Pull)
func WithConfig(cfg *Configuration) PullOpt {
return func(p *Pull) {
p.cfg = cfg
}
}
// NewPull creates a new Pull object.
func NewPull() *Pull { func NewPull() *Pull {
return &Pull{} return NewPullWithOpts()
}
// NewPullWithOpts creates a new pull, with configuration options.
func NewPullWithOpts(opts ...PullOpt) *Pull {
p := &Pull{}
for _, fn := range opts {
fn(p)
}
return p
} }
// Run executes 'helm pull' against the given release. // Run executes 'helm pull' against the given release.
@ -70,6 +89,16 @@ func (p *Pull) Run(chartRef string) (string, error) {
RepositoryCache: p.Settings.RepositoryCache, RepositoryCache: p.Settings.RepositoryCache,
} }
if strings.HasPrefix(chartRef, "oci://") {
if p.Version == "" {
return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
}
c.Options = append(c.Options,
getter.WithRegistryClient(p.cfg.RegistryClient),
getter.WithTagName(p.Version))
}
if p.Verify { if p.Verify {
c.Verify = downloader.VerifyAlways c.Verify = downloader.VerifyAlways
} else if p.VerifyLater { } else if p.VerifyLater {
@ -123,6 +152,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
_, chartName := filepath.Split(chartRef) _, chartName := filepath.Split(chartRef)
udCheck = filepath.Join(udCheck, chartName) udCheck = filepath.Join(udCheck, chartName)
} }
if _, err := os.Stat(udCheck); err != nil { if _, err := os.Stat(udCheck); err != nil {
if err := os.MkdirAll(udCheck, 0755); err != nil { if err := os.MkdirAll(udCheck, 0755); err != nil {
return out.String(), errors.Wrap(err, "failed to untar (mkdir)") return out.String(), errors.Wrap(err, "failed to untar (mkdir)")

@ -37,12 +37,14 @@ type ReleaseTesting struct {
Timeout time.Duration Timeout time.Duration
// Used for fetching logs from test pods // Used for fetching logs from test pods
Namespace string Namespace string
Filters map[string][]string
} }
// NewReleaseTesting creates a new ReleaseTesting object with the given configuration. // NewReleaseTesting creates a new ReleaseTesting object with the given configuration.
func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { func NewReleaseTesting(cfg *Configuration) *ReleaseTesting {
return &ReleaseTesting{ return &ReleaseTesting{
cfg: cfg, cfg: cfg,
Filters: map[string][]string{},
} }
} }
@ -62,11 +64,37 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
return rel, err return rel, err
} }
skippedHooks := []*release.Hook{}
executingHooks := []*release.Hook{}
if len(r.Filters["!name"]) != 0 {
for _, h := range rel.Hooks {
if contains(r.Filters["!name"], h.Name) {
skippedHooks = append(skippedHooks, h)
} else {
executingHooks = append(executingHooks, h)
}
}
rel.Hooks = executingHooks
}
if len(r.Filters["name"]) != 0 {
executingHooks = nil
for _, h := range rel.Hooks {
if contains(r.Filters["name"], h.Name) {
executingHooks = append(executingHooks, h)
} else {
skippedHooks = append(skippedHooks, h)
}
}
rel.Hooks = executingHooks
}
if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil {
rel.Hooks = append(skippedHooks, rel.Hooks...)
r.cfg.Releases.Update(rel) r.cfg.Releases.Update(rel)
return rel, err return rel, err
} }
rel.Hooks = append(skippedHooks, rel.Hooks...)
return rel, r.cfg.Releases.Update(rel) return rel, r.cfg.Releases.Update(rel)
} }
@ -99,3 +127,12 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
} }
return nil return nil
} }
func contains(arr []string, value string) bool {
for _, item := range arr {
if item == value {
return true
}
}
return false
}

@ -38,6 +38,7 @@ type Rollback struct {
Version int Version int
Timeout time.Duration Timeout time.Duration
Wait bool Wait bool
WaitForJobs bool
DisableHooks bool DisableHooks bool
DryRun bool DryRun bool
Recreate bool // will (if true) recreate pods after a rollback. Recreate bool // will (if true) recreate pods after a rollback.
@ -199,11 +200,20 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
} }
if r.Wait { if r.Wait {
if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { if r.WaitForJobs {
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil {
r.cfg.recordRelease(currentRelease) targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
r.cfg.recordRelease(targetRelease) r.cfg.recordRelease(currentRelease)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) r.cfg.recordRelease(targetRelease)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
}
} else {
if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil {
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
}
} }
} }

@ -1,6 +1,6 @@
dependencies: dependencies:
- name: mariadb - name: mariadb
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://charts.helm.sh/stable/
version: 4.3.1 version: 4.3.1
digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26
generated: 2018-08-02T22:07:51.905271776Z generated: 2018-08-02T22:07:51.905271776Z

@ -1,7 +1,7 @@
dependencies: dependencies:
- name: mariadb - name: mariadb
version: 4.x.x version: 4.x.x
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://charts.helm.sh/stable/
condition: mariadb.enabled condition: mariadb.enabled
tags: tags:
- wordpress-database - wordpress-database

@ -1,6 +1,6 @@
dependencies: dependencies:
- name: mariadb - name: mariadb
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://charts.helm.sh/stable/
version: 4.3.1 version: 4.3.1
digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26
generated: 2018-08-02T22:07:51.905271776Z generated: 2018-08-02T22:07:51.905271776Z

@ -1,7 +1,7 @@
dependencies: dependencies:
- name: mariadb - name: mariadb
version: 4.x.x version: 4.x.x
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://charts.helm.sh/stable/
condition: mariadb.enabled condition: mariadb.enabled
tags: tags:
- wordpress-database - wordpress-database

@ -1,6 +1,6 @@
dependencies: dependencies:
- name: mariadb - name: mariadb
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://charts.helm.sh/stable/
version: 4.3.1 version: 4.3.1
digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26
generated: 2018-08-02T22:07:51.905271776Z generated: 2018-08-02T22:07:51.905271776Z

@ -1,7 +1,7 @@
dependencies: dependencies:
- name: mariadb - name: mariadb
version: 4.x.x version: 4.x.x
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://charts.helm.sh/stable/
condition: mariadb.enabled condition: mariadb.enabled
tags: tags:
- wordpress-database - wordpress-database

@ -1,3 +1,3 @@
NAME VERSION REPOSITORY STATUS NAME VERSION REPOSITORY STATUS
mariadb 4.x.x https://kubernetes-charts.storage.googleapis.com/ ok mariadb 4.x.x https://charts.helm.sh/stable/ ok

@ -1,3 +1,3 @@
NAME VERSION REPOSITORY STATUS NAME VERSION REPOSITORY STATUS
mariadb 4.x.x https://kubernetes-charts.storage.googleapis.com/ missing mariadb 4.x.x https://charts.helm.sh/stable/ missing

@ -1,3 +1,3 @@
NAME VERSION REPOSITORY STATUS NAME VERSION REPOSITORY STATUS
mariadb 4.x.x https://kubernetes-charts.storage.googleapis.com/ unpacked mariadb 4.x.x https://charts.helm.sh/stable/ unpacked

@ -65,6 +65,8 @@ type Upgrade struct {
Timeout time.Duration Timeout time.Duration
// Wait determines whether the wait operation should be performed after the upgrade is requested. // Wait determines whether the wait operation should be performed after the upgrade is requested.
Wait bool Wait bool
// WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested.
WaitForJobs bool
// DisableHooks disables hook processing if set to true. // DisableHooks disables hook processing if set to true.
DisableHooks bool DisableHooks bool
// DryRun controls whether the operation is prepared, but not executed. // DryRun controls whether the operation is prepared, but not executed.
@ -331,9 +333,16 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
} }
if u.Wait { if u.Wait {
if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { if u.WaitForJobs {
u.cfg.recordRelease(originalRelease) if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil {
return u.failRelease(upgradedRelease, results.Created, err) u.cfg.recordRelease(originalRelease)
return u.failRelease(upgradedRelease, results.Created, err)
}
} else {
if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
u.cfg.recordRelease(originalRelease)
return u.failRelease(upgradedRelease, results.Created, err)
}
} }
} }
@ -402,6 +411,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
rollin := NewRollback(u.cfg) rollin := NewRollback(u.cfg)
rollin.Version = filteredHistory[0].Version rollin.Version = filteredHistory[0].Version
rollin.Wait = true rollin.Wait = true
rollin.WaitForJobs = u.WaitForJobs
rollin.DisableHooks = u.DisableHooks rollin.DisableHooks = u.DisableHooks
rollin.Recreate = u.Recreate rollin.Recreate = u.Recreate
rollin.Force = u.Force rollin.Force = u.Force

@ -60,6 +60,29 @@ func TestUpgradeRelease_Wait(t *testing.T) {
is.Equal(res.Info.Status, release.StatusFailed) is.Equal(res.Info.Status, release.StatusFailed)
} }
func TestUpgradeRelease_WaitForJobs(t *testing.T) {
is := assert.New(t)
req := require.New(t)
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Info.Status = release.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out")
upAction.cfg.KubeClient = failer
upAction.Wait = true
upAction.WaitForJobs = true
vals := map[string]interface{}{}
res, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
}
func TestUpgradeRelease_CleanupOnFail(t *testing.T) { func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)

@ -49,6 +49,23 @@ type Dependency struct {
Alias string `json:"alias,omitempty"` Alias string `json:"alias,omitempty"`
} }
// Validate checks for common problems with the dependency datastructure in
// the chart. This check must be done at load time before the dependency's charts are
// loaded.
func (d *Dependency) Validate() error {
d.Name = sanitizeString(d.Name)
d.Version = sanitizeString(d.Version)
d.Repository = sanitizeString(d.Repository)
d.Condition = sanitizeString(d.Condition)
for i := range d.Tags {
d.Tags[i] = sanitizeString(d.Tags[i])
}
if d.Alias != "" && !aliasNameFormat.MatchString(d.Alias) {
return ValidationErrorf("dependency %q has disallowed characters in the alias", d.Name)
}
return nil
}
// Lock is a lock file for dependencies. // Lock is a lock file for dependencies.
// //
// It represents the state that the dependencies should be in. // It represents the state that the dependencies should be in.

@ -0,0 +1,44 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chart
import (
"testing"
)
func TestValidateDependency(t *testing.T) {
dep := &Dependency{
Name: "example",
}
for value, shouldFail := range map[string]bool{
"abcdefghijklmenopQRSTUVWXYZ-0123456780_": false,
"-okay": false,
"_okay": false,
"- bad": true,
" bad": true,
"bad\nvalue": true,
"bad ": true,
"bad$": true,
} {
dep.Alias = value
res := dep.Validate()
if res != nil && !shouldFail {
t.Errorf("Failed on case %q", dep.Alias)
} else if res == nil && shouldFail {
t.Errorf("Expected failure for %q", dep.Alias)
}
}
}

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

Loading…
Cancel
Save