Resolve conflicts

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

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

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

@ -6,10 +6,11 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v3.0.14
with:
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.'
exempt-issue-labels: 'keep+open,v4.x'
exempt-issue-labels: 'keep open,v4.x'
days-before-stale: 90
days-before-close: 30
operations-per-run: 100

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

@ -5,9 +5,12 @@ TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/pp
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum
BINNAME ?= helm
GOPATH = $(shell go env GOPATH)
GOX = $(GOPATH)/bin/gox
GOIMPORTS = $(GOPATH)/bin/goimports
GOBIN = $(shell go env GOBIN)
ifeq ($(GOBIN),)
GOBIN = $(shell go env GOPATH)/bin
endif
GOX = $(GOBIN)/gox
GOIMPORTS = $(GOBIN)/goimports
ARCH = $(shell uname -p)
ACCEPTANCE_DIR:=../acceptance-testing
@ -21,7 +24,9 @@ TESTS := .
TESTFLAGS :=
LDFLAGS := -w -s
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
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 += $(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
all: build
@ -98,7 +113,7 @@ test-coverage:
.PHONY: test-style
test-style:
GO111MODULE=on golangci-lint run
GO111MODULE=on golangci-lint run --timeout 5m0s
@scripts/validate-license.sh
.PHONY: test-acceptance
@ -124,6 +139,13 @@ coverage:
format: $(GOIMPORTS)
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

@ -9,7 +9,7 @@ Helm is a tool for managing Charts. Charts are packages of pre-configured Kubern
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
- Create reproducible builds of your Kubernetes applications
- 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)
- Mailing List:
- [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

@ -27,7 +27,7 @@ import (
)
const completionDesc = `
Generate autocompletions script for Helm for the specified shell.
Generate autocompletion scripts for Helm for the specified shell.
`
const bashCompDesc = `
Generate the autocompletion script for Helm for the bash shell.
@ -74,15 +74,14 @@ var disableCompDescriptions bool
func newCompletionCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "completion",
Short: "generate autocompletions script for the specified shell",
Short: "generate autocompletion scripts for the specified shell",
Long: completionDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
}
bash := &cobra.Command{
Use: "bash",
Short: "generate autocompletions script for bash",
Short: "generate autocompletion script for bash",
Long: bashCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
@ -94,22 +93,21 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
zsh := &cobra.Command{
Use: "zsh",
Short: "generate autocompletions script for zsh",
Short: "generate autocompletion script for zsh",
Long: zshCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionZsh(out, cmd)
},
}
zsh.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
fish := &cobra.Command{
Use: "fish",
Short: "generate autocompletions script for fish",
Short: "generate autocompletion script for fish",
Long: fishCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionFish(out, cmd)
@ -145,148 +143,29 @@ fi
}
func runCompletionZsh(out io.Writer, cmd *cobra.Command) error {
zshInitialization := `#compdef helm
__helm_bash_source() {
alias shopt=':'
alias _expand=_bash_expand
alias _complete=_bash_comp
emulate -L sh
setopt kshglob noshglob braceexpand
source "$@"
}
__helm_type() {
# -t is not supported by zsh
if [ "$1" == "-t" ]; then
shift
# fake Bash 4 to disable "complete -o nospace". Instead
# "compopt +-o nospace" is used in the code to toggle trailing
# spaces. We don't support that, but leave trailing spaces on
# all the time
if [ "$1" = "__helm_compopt" ]; then
echo builtin
return 0
fi
fi
type "$@"
var err error
if disableCompDescriptions {
err = cmd.Root().GenZshCompletionNoDesc(out)
} else {
err = cmd.Root().GenZshCompletion(out)
}
__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'
// In case the user renamed the helm binary (e.g., to be able to run
// both helm2 and helm3), we hook the new binary name to the completion function
if binary := filepath.Base(os.Args[0]); binary != "helm" {
renamedBinaryHook := `
# Hook the command used to generate the completion script
# to the helm completion function to handle the case where
# the user renamed the helm binary
compdef _helm %[1]s
`
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 := `
BASH_COMPLETION_EOF
}
__helm_bash_source <(__helm_convert_bash_to_zsh)
`
out.Write([]byte(zshTail))
return nil
return err
}
func runCompletionFish(out io.Writer, cmd *cobra.Command) error {

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

@ -82,19 +82,18 @@ the contents of a chart.
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{
Use: "dependency update|build|list",
Aliases: []string{"dep", "dependencies"},
Short: "manage a chart's dependencies",
Long: dependencyDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
}
cmd.AddCommand(newDependencyListCmd(out))
cmd.AddCommand(newDependencyUpdateCmd(out))
cmd.AddCommand(newDependencyBuildCmd(out))
cmd.AddCommand(newDependencyUpdateCmd(cfg, out))
cmd.AddCommand(newDependencyBuildCmd(cfg, out))
return cmd
}

@ -41,7 +41,7 @@ If no lock file is found, 'helm dependency build' will mirror the behavior
of 'helm dependency update'.
`
func newDependencyBuildCmd(out io.Writer) *cobra.Command {
func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewDependency()
cmd := &cobra.Command{
@ -58,7 +58,9 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
Out: out,
ChartPath: chartpath,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
@ -77,6 +79,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
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
}

@ -22,6 +22,7 @@ import (
"strings"
"testing"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest"
@ -37,6 +38,27 @@ func TestDependencyBuildCmd(t *testing.T) {
rootDir := srv.Root()
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"
createTestingChart(t, rootDir, chartname, srv.URL())
repoFile := filepath.Join(rootDir, "repositories.yaml")
@ -99,6 +121,35 @@ func TestDependencyBuildCmd(t *testing.T) {
if v := reqver.Version; v != "0.1.0" {
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) {

@ -43,7 +43,7 @@ in the Chart.yaml file, but (b) at the wrong version.
`
// 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()
cmd := &cobra.Command{
@ -63,6 +63,7 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,

@ -40,6 +40,23 @@ func TestDependencyUpdateCmd(t *testing.T) {
defer srv.Stop()
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 {
t.Fatal(err)
}
@ -115,6 +132,22 @@ func TestDependencyUpdateCmd(t *testing.T) {
if _, err := os.Stat(unexpected); err == nil {
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) {
@ -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
//
// The baseURL can be used to point to a particular repository server.

@ -16,8 +16,11 @@ limitations under the License.
package main
import (
"fmt"
"io"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -41,6 +44,7 @@ type docsOptions struct {
dest string
docTypeString string
topCmd *cobra.Command
generateHeaders bool
}
func newDocsCmd(out io.Writer) *cobra.Command {
@ -62,6 +66,18 @@ func newDocsCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
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.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files")
cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{"bash", "man", "markdown"}
var comps []string
for _, t := range types {
if strings.HasPrefix(t, toComplete) {
comps = append(comps, t)
}
}
return comps, cobra.ShellCompDirectiveNoFileComp
})
return cmd
}
@ -69,6 +85,18 @@ func newDocsCmd(out io.Writer) *cobra.Command {
func (o *docsOptions) run(out io.Writer) error {
switch o.docTypeString {
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)
case "man":
manHdr := &doc.GenManHeader{Title: "HELM", Section: "1"}

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

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

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

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

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

@ -140,6 +140,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
f.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.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.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&client.Description, "description", "", "add a custom description")

@ -85,6 +85,12 @@ func TestInstall(t *testing.T) {
cmd: "install apollo testdata/testcharts/empty --wait",
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
{
name: "install with name-template",

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

@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) {
known := []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 {
for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") {

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

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

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

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

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

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"log"
"strings"
"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.
`
func newPullCmd(out io.Writer) *cobra.Command {
client := action.NewPull()
func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPullWithOpts(action.WithConfig(cfg))
cmd := &cobra.Command{
Use: "pull [chart URL | repo/chartname] [...]",
@ -64,6 +65,12 @@ func newPullCmd(out io.Writer) *cobra.Command {
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++ {
output, err := client.Run(args[i])
if err != nil {

@ -32,6 +32,13 @@ func TestPullCmd(t *testing.T) {
}
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 {
t.Fatal(err)
}
@ -139,23 +146,70 @@ func TestPullCmd(t *testing.T) {
failExpect: "Failed to fetch chart version",
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 {
t.Run(tt.name, func(t *testing.T) {
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,
outdir,
filepath.Join(outdir, "repositories.yaml"),
outdir,
filepath.Join(outdir, "config.json"),
)
// Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182
if tt.existFile != "" {
file := filepath.Join(outdir, tt.existFile)
_, err := os.Create(file)
if err != nil {
t.Fatal("err")
t.Fatal(err)
}
}
if tt.existDir != "" {

@ -19,6 +19,8 @@ package main
import (
"fmt"
"io"
"regexp"
"strings"
"time"
"github.com/spf13/cobra"
@ -39,6 +41,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
client := action.NewReleaseTesting(cfg)
var outfmt = output.Table
var outputLogs bool
var filter []string
cmd := &cobra.Command{
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 {
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])
// 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
@ -80,6 +91,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
f := cmd.Flags()
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.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
}

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

@ -29,7 +29,7 @@ import (
"github.com/gofrs/flock"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require"
@ -37,12 +37,19 @@ import (
"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 {
name string
url string
username string
password string
forceUpdate bool
allowDeprecatedRepos bool
certFile string
keyFile string
@ -83,11 +90,21 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.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.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
}
func (o *repoAddOptions) run(out io.Writer) error {
// Block deprecated repos
if !o.allowDeprecatedRepos {
for oldURL, newURL := range deprecatedRepos {
if strings.Contains(o.url, oldURL) {
return fmt.Errorf("repo %q is no longer available; try %q instead", o.url, newURL)
}
}
}
// Ensure the file directory exists as it is required for file locking
err := os.MkdirAll(filepath.Dir(o.repoFile), os.ModePerm)
if err != nil && !os.IsExist(err) {
@ -119,7 +136,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
if o.username != "" && o.password == "" {
fd := int(os.Stdin.Fd())
fmt.Fprint(out, "Password: ")
password, err := terminal.ReadPassword(fd)
password, err := term.ReadPassword(fd)
fmt.Fprintln(out)
if err != nil {
return err

@ -63,9 +63,15 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
func (o *repoUpdateOptions) run(out io.Writer) error {
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
}
var repos []*repo.ChartRepository
for _, cfg := range f.Repositories {
r, err := repo.NewChartRepository(cfg, getter.All(settings))

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

@ -21,6 +21,7 @@ import (
"fmt"
"io"
"log"
"os"
"strings"
"github.com/spf13/cobra"
@ -30,6 +31,7 @@ import (
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/repo"
)
var globalUsage = `The Kubernetes package manager
@ -60,8 +62,9 @@ Environment variables:
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBEASGROUPS | set the Username to impersonate for the operation. |
| $HELM_KUBEASUSER | set the Groups to use for impoersonation using a comma-separated list. |
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
| $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_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.",
Long: globalUsage,
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()
@ -131,13 +131,13 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
ctxs := []string{}
for name := range config.Contexts {
comps := []string{}
for name, context := range config.Contexts {
if strings.HasPrefix(name, toComplete) {
ctxs = append(ctxs, name)
comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
}
}
return ctxs, cobra.ShellCompDirectiveNoFileComp
return comps, cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
})
@ -153,12 +153,22 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true
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
cmd.AddCommand(
// chart commands
newCreateCmd(out),
newDependencyCmd(out),
newPullCmd(out),
newDependencyCmd(actionConfig, out),
newPullCmd(actionConfig, out),
newShowCmd(out),
newLintCmd(out),
newPackageCmd(out),
@ -188,15 +198,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
)
// 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(
newRegistryCmd(actionConfig, out),
newChartCmd(actionConfig, out),
@ -208,5 +209,56 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Check permissions on critical files
checkPerms()
// Check for expired repositories
checkForExpiredRepos(settings.RepositoryConfig)
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
import (
"bufio"
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
@ -27,15 +28,27 @@ import (
"testing"
)
func TestCheckPerms(t *testing.T) {
// NOTE(bacongobbler): have to open a new file handler here as the default os.Sterr cannot be read from
stderr, err := os.Open("/dev/stderr")
func checkPermsStderr() (string, error) {
r, w, err := os.Pipe()
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")
if err != nil {
t.Fatal(err)
@ -51,8 +64,7 @@ func TestCheckPerms(t *testing.T) {
settings.KubeConfig = tfile
defer func() { settings.KubeConfig = tconfig }()
checkPerms()
text, err := reader.ReadString('\n')
text, err := checkPermsStderr()
if err != nil {
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 {
t.Errorf("Could not change mode on file: %s", err)
}
checkPerms()
text, err = reader.ReadString('\n')
text, err = checkPermsStderr()
if err != nil {
t.Fatalf("could not read from stderr: %s", err)
}

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

@ -30,15 +30,23 @@ import (
)
const searchHubDesc = `
Search the Helm Hub or an instance of Monocular for Helm charts.
The Helm Hub provides a centralized search for publicly available distributed
charts. It is maintained by the Helm project. It can be visited at
https://hub.helm.sh
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
Helm Hub. You can find it at https://github.com/helm/monocular
Search for Helm charts in the Artifact Hub or your own hub instance.
Artifact Hub is a web-based application that enables finding, installing, and
publishing packages and configurations for CNCF projects, including publicly
available distributed charts Helm charts. It is a Cloud Native Computing
Foundation sandbox project. You can browse the hub at https://artifacthub.io/
The [KEYWORD] argument accepts either a keyword string, or quoted string of rich
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 {
@ -51,8 +59,8 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
o := &searchHubOptions{}
cmd := &cobra.Command{
Use: "hub [keyword]",
Short: "search for charts in the Helm Hub or an instance of Monocular",
Use: "hub [KEYWORD]",
Short: "search for charts in the Artifact Hub or your own hub instance",
Long: searchHubDesc,
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(out, args)
@ -60,7 +68,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
}
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")
bindOutputFlag(cmd, &o.outputFormat)
@ -98,7 +106,14 @@ type hubSearchWriter struct {
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter {
var elements []hubChartElement
for _, r := range results {
// Backwards compatibility for Monocular
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})
}
return &hubSearchWriter{elements, columnWidth}

@ -26,7 +26,7 @@ import (
func TestSearchHubCmd(t *testing.T) {
// 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) {
fmt.Fprintln(w, searchResult)
}))

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

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

@ -27,6 +27,8 @@ import (
"sort"
"strings"
"helm.sh/helm/v3/pkg/release"
"github.com/spf13/cobra"
"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 {
var validate bool
var includeCrds bool
var skipTests bool
client := action.NewInstall(cfg)
valueOpts := &values.Options{}
var extraAPIs []string
@ -84,6 +87,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if !client.DisableHooks {
fileWritten := make(map[string]bool)
for _, m := range rel.Hooks {
if skipTests && isTestHook(m) {
continue
}
if client.OutputDir == "" {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
} 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.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(&skipTests, "skip-tests", false, "skip tests from templated output")
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.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
}
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)
// 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

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

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

@ -2,8 +2,10 @@ apiVersion: v1
entries:
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
created: "2018-06-27T10:00:18.230700509Z"
deprecated: true
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
@ -13,9 +15,11 @@ entries:
keywords: []
maintainers: []
icon: ""
apiVersion: v2
- 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
created: "2018-07-09T11:34:37.797864902Z"
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
@ -25,9 +29,11 @@ entries:
keywords: []
maintainers: []
icon: ""
apiVersion: v2
- 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
created: "2020-11-12T08:44:58.872726222Z"
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
@ -37,10 +43,12 @@ entries:
keywords: []
maintainers: []
icon: ""
apiVersion: v2
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
created: "2018-04-23T08:20:27.160959131Z"
home: https://mariadb.org
sources:
- https://github.com/bitnami/bitnami-docker-mariadb
@ -55,3 +63,4 @@ entries:
- name: Bitnami
email: containers@bitnami.com
icon: ""
apiVersion: v2

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -11,7 +11,8 @@ kind: Role
metadata:
name: subchart-role
rules:
- resources: ["*"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "foobar-YWJj-baz"
kube-version/major: "1"
kube-version/minor: "18"
kube-version/version: "v1.18.0"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
@ -82,3 +83,32 @@ spec:
name: nginx
selector:
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:
name: subchart-role
rules:
- resources: ["*"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
@ -71,8 +72,8 @@ metadata:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME"
kube-version/major: "1"
kube-version/minor: "18"
kube-version/version: "v1.18.0"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
@ -82,3 +83,32 @@ spec:
name: apache
selector:
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:
name: subchart-role
rules:
- resources: ["*"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml

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

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

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

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

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

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

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

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

@ -1,4 +1,4 @@
apiVersion: v1
repositories:
- 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
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
appVersion: 1.16.0
# incremented each time you make changes to the application and it is recommended to use it with quotes.
appVersion: "1.16.0"

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

@ -3,5 +3,6 @@ kind: Role
metadata:
name: {{ .Chart.Name }}-role
rules:
- resources: ["*"]
- apiGroups: [""]
resources: ["pods"]
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.Timeout = client.Timeout
instClient.Wait = client.Wait
instClient.WaitForJobs = client.WaitForJobs
instClient.Devel = client.Devel
instClient.Namespace = client.Namespace
instClient.Atomic = client.Atomic
@ -179,6 +180,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&client.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.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.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")

@ -131,6 +131,12 @@ func TestUpgradeCmd(t *testing.T) {
golden: "output/upgrade-with-wait.txt",
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",
cmd: fmt.Sprintf("upgrade bonkers-bunny %s", missingDepsPath),

@ -1,19 +1,19 @@
module helm.sh/helm/v3
go 1.14
go 1.15
require (
github.com/BurntSushi/toml v0.3.1
github.com/DATA-DOG/go-sqlmock v1.4.1
github.com/Masterminds/goutils v1.1.0
github.com/Masterminds/semver/v3 v3.1.0
github.com/Masterminds/sprig/v3 v3.1.0
github.com/Masterminds/squirrel v1.4.0
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/goutils v1.1.1
github.com/Masterminds/semver/v3 v3.1.1
github.com/Masterminds/sprig/v3 v3.2.2
github.com/Masterminds/squirrel v1.5.0
github.com/Masterminds/vcs v1.13.1
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/deislabs/oras v0.8.1
github.com/deislabs/oras v0.10.0
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce
github.com/docker/go-units v0.4.0
@ -21,32 +21,34 @@ require (
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.0
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/lib/pq v1.8.0
github.com/mattn/go-shellwords v1.0.10
github.com/mitchellh/copystructure v1.0.0
github.com/lib/pq v1.9.0
github.com/mattn/go-shellwords v1.0.11
github.com/mitchellh/copystructure v1.1.1
github.com/opencontainers/go-digest v1.0.0
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/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
github.com/sirupsen/logrus v1.6.0
github.com/spf13/cobra v1.0.0
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v1.1.1
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
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
k8s.io/api v0.19.2
k8s.io/apiextensions-apiserver v0.19.2
k8s.io/apimachinery v0.19.2
k8s.io/cli-runtime v0.19.2
k8s.io/client-go v0.19.2
k8s.io/klog v1.0.0
k8s.io/kubectl v0.19.2
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221
k8s.io/api v0.20.2
k8s.io/apiextensions-apiserver v0.20.2
k8s.io/apimachinery v0.20.2
k8s.io/apiserver v0.20.2
k8s.io/cli-runtime v0.20.2
k8s.io/client-go v0.20.2
k8s.io/klog/v2 v2.5.0
k8s.io/kubectl v0.20.2
sigs.k8s.io/yaml v1.2.0
)
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/docker => github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
)

606
go.sum

File diff suppressed because it is too large Load Diff

@ -17,6 +17,7 @@ limitations under the License.
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"bytes"
"context"
"fmt"
"io"
@ -25,6 +26,7 @@ import (
"sort"
auth "github.com/deislabs/oras/pkg/auth/docker"
"github.com/deislabs/oras/pkg/content"
"github.com/deislabs/oras/pkg/oras"
"github.com/gosuri/uitable"
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
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 == "" {
return errors.New("tag explicitly required")
}

@ -22,7 +22,6 @@ import (
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
@ -33,12 +32,12 @@ import (
"time"
"github.com/containerd/containerd/errdefs"
auth "github.com/deislabs/oras/pkg/auth/docker"
"github.com/docker/distribution/configuration"
"github.com/docker/distribution/registry"
_ "github.com/docker/distribution/registry/auth/htpasswd"
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
"github.com/phayes/freeport"
"github.com/stretchr/testify/suite"
"golang.org/x/crypto/bcrypt"
@ -107,7 +106,7 @@ func (suite *RegistryClientTestSuite) SetupSuite() {
// Registry config
config := &configuration.Configuration{}
port, err := getFreePort()
port, err := freeport.GetFreePort()
suite.Nil(err, "no error finding free port for test registry")
suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port)
config.HTTP.Addr = fmt.Sprintf(":%d", port)
@ -202,13 +201,13 @@ func (suite *RegistryClientTestSuite) Test_4_PullChart() {
// non-existent ref
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
suite.Nil(err)
err = suite.RegistryClient.PullChart(ref)
_, err = suite.RegistryClient.PullChart(ref)
suite.NotNil(err)
// existing ref
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
suite.Nil(err)
err = suite.RegistryClient.PullChart(ref)
_, err = suite.RegistryClient.PullChart(ref)
suite.Nil(err)
}
@ -245,7 +244,7 @@ func (suite *RegistryClientTestSuite) Test_8_ManInTheMiddle() {
suite.Nil(err)
// returns content that does not match the expected digest
err = suite.RegistryClient.PullChart(ref)
_, err = suite.RegistryClient.PullChart(ref)
suite.NotNil(err)
suite.True(errdefs.IsFailedPrecondition(err))
}
@ -254,21 +253,6 @@ func TestRegistryClientTestSuite(t *testing.T) {
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 {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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.
*/
// Package monocular contains the logic for interacting with monocular instances
// like the Helm Hub.
// Package monocular contains the logic for interacting with a Monocular
// 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

@ -40,12 +40,18 @@ const SearchPath = "api/chartsvc/v1/charts/search"
// SearchResult represents an individual chart result
type SearchResult struct {
ID string `json:"id"`
ArtifactHub ArtifactHub `json:"artifactHub"`
Type string `json:"type"`
Attributes Chart `json:"attributes"`
Links Links `json:"links"`
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
type Chart struct {
Name string `json:"name"`

@ -24,7 +24,7 @@ import (
)
// 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) {

@ -28,11 +28,14 @@ import (
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/repo"
)
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
// Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct {
chartpath string
@ -88,6 +91,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
}
continue
}
constraint, err := semver.NewConstraint(d.Version)
if err != nil {
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
}
var vs repo.ChartVersions
var version string
var ok bool
found := true
if !strings.HasPrefix(d.Repository, "oci://") {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
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 {
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{
Name: d.Name,
Repository: d.Repository,
Version: version,
}
found := false
// The version are already sorted and hence the first one to satisfy the constraint is used
for _, ver := range vs {
v, err := semver.NewVersion(ver.Version)

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

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

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

@ -40,5 +40,5 @@ func (a *ChartPull) Run(out io.Writer, ref string) error {
if err != nil {
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.
//
// 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 {
cfg *Configuration

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

Loading…
Cancel
Save