Merge remote-tracking branch 'upstream/main'

Signed-off-by: Dmitrii Ermakov <demonihin@gmail.com>
pull/9119/head
Dmitrii Ermakov 4 years ago
commit 69996fe309

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

@ -26,7 +26,7 @@ fi
VERSION= VERSION=
if [[ -n "${CIRCLE_TAG:-}" ]]; then if [[ -n "${CIRCLE_TAG:-}" ]]; then
VERSION="${CIRCLE_TAG}" VERSION="${CIRCLE_TAG}"
elif [[ "${CIRCLE_BRANCH:-}" == "master" ]]; then elif [[ "${CIRCLE_BRANCH:-}" == "main" ]]; then
VERSION="canary" VERSION="canary"
else else
echo "Skipping deploy step; this is neither a releasable branch or a tag" echo "Skipping deploy step; this is neither a releasable branch or a tag"

@ -1,5 +1,5 @@
<!-- Thanks for sending a pull request! Here are some tips for you: <!-- Thanks for sending a pull request! Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/master/CONTRIBUTING.md 1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.--> 2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->
**What this PR does / why we need it**: **What this PR does / why we need it**:

@ -0,0 +1,29 @@
name: build-pr
on:
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: '1.16'
- name: Install golangci-lint
run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
shasum -a 256 golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz | grep "^$GOLANGCI_LINT_SHA256 " > /dev/null
tar -xf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env:
GOLANGCI_LINT_VERSION: '1.36.0'
GOLANGCI_LINT_SHA256: '9b8856b3a1c9bfbcf3a06b78e94611763b79abd9751c245246787cd3bf0e78a5'
- name: Test style
run: make test-style
- name: Run unit tests
run: make test-coverage

@ -13,10 +13,10 @@ name: "CodeQL"
on: on:
push: push:
branches: [ master ] branches: [ main ]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ master ] branches: [ main ]
schedule: schedule:
- cron: '29 6 * * 6' - cron: '29 6 * * 6'

1
.gitignore vendored

@ -1,4 +1,5 @@
*.exe *.exe
*.swp
.DS_Store .DS_Store
.coverage/ .coverage/
.idea/ .idea/

@ -1,5 +1,5 @@
run: run:
timeout: 2m timeout: 10m
linters: linters:
disable-all: true disable-all: true

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

88
KEYS

@ -852,3 +852,91 @@ ANQIfZg7P8oNxVDAX+jIsTDxjh8r+S1wsUQcTNop6JMicDbxrBRB13vYIY0Jg4+Z
hS0eN8yqaR533ire0Ur5Vif6+z4A0ifVTZ2hY96B hS0eN8yqaR533ire0Ur5Vif6+z4A0ifVTZ2hY96B
=nEJu =nEJu
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
pub rsa4096 2021-03-12 [SC] [expires: 2037-03-08]
4AB45F1CB0D292975C6371436E2A23D806B6E6DD
uid [ unknown] Matt Butcher <technosophos@gmail.com>
sig 3 6E2A23D806B6E6DD 2021-03-12 Matt Butcher <technosophos@gmail.com>
uid [ unknown] Matt Butcher <matt.butcher@microsoft.com>
sig 3 6E2A23D806B6E6DD 2021-03-12 Matt Butcher <technosophos@gmail.com>
uid [ unknown] Matt Butcher <mabutch@microsoft.com>
sig 3 6E2A23D806B6E6DD 2021-03-12 Matt Butcher <technosophos@gmail.com>
sub rsa4096 2021-03-12 [E] [expires: 2037-03-08]
sig 6E2A23D806B6E6DD 2021-03-12 Matt Butcher <technosophos@gmail.com>
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
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
=h3+3
-----END PGP PUBLIC KEY BLOCK-----

@ -1,8 +1,8 @@
BINDIR := $(CURDIR)/bin BINDIR := $(CURDIR)/bin
INSTALL_PATH ?= /usr/local/bin INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64 TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum
BINNAME ?= helm BINNAME ?= helm
GOBIN = $(shell go env GOBIN) GOBIN = $(shell go env GOBIN)
@ -25,7 +25,7 @@ TESTFLAGS :=
LDFLAGS := -w -s LDFLAGS := -w -s
GOFLAGS := GOFLAGS :=
# Rebuild the buinary if any of these files change # Rebuild the binary if any of these files change
SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum
# Required for globs to work correctly # Required for globs to work correctly
@ -77,7 +77,7 @@ all: build
build: $(BINDIR)/$(BINNAME) build: $(BINDIR)/$(BINNAME)
$(BINDIR)/$(BINNAME): $(SRC) $(BINDIR)/$(BINNAME): $(SRC)
GO111MODULE=on go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm GO111MODULE=on go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# install # install
@ -165,7 +165,7 @@ $(GOIMPORTS):
.PHONY: build-cross .PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static" build-cross: LDFLAGS += -extldflags "-static"
build-cross: $(GOX) build-cross: $(GOX)
GO111MODULE=on CGO_ENABLED=0 $(GOX) -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm GOFLAGS="-trimpath" GO111MODULE=on CGO_ENABLED=0 $(GOX) -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm
.PHONY: dist .PHONY: dist
dist: dist:

@ -1,7 +1,6 @@
maintainers: maintainers:
- adamreese - adamreese
- bacongobbler - bacongobbler
- fibonacci1729
- hickeyma - hickeyma
- jdolitsky - jdolitsky
- marckhouzam - marckhouzam
@ -9,13 +8,14 @@ maintainers:
- prydonius - prydonius
- SlickNik - SlickNik
- technosophos - technosophos
- thomastaylor312
- viglesiasce
emeritus: emeritus:
- fibonacci1729
- jascott1 - jascott1
- michelleN - michelleN
- migmartri - migmartri
- nebril - nebril
- rimusz
- seh - seh
- thomastaylor312
- vaikas-google - vaikas-google
- rimusz - viglesiasce

@ -54,7 +54,7 @@ Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/)
## Roadmap ## Roadmap
The [Helm roadmap uses Github milestones](https://github.com/helm/helm/milestones) to track the progress of the project. The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project.
## Community, discussion, contribution, and support ## Community, discussion, contribution, and support

@ -31,14 +31,18 @@ Charts are sorted by ref name, alphabetically.
` `
func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ chartList := action.NewChartList(cfg)
cmd := &cobra.Command{
Use: "list", Use: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "list all saved charts", Short: "list all saved charts",
Long: chartListDesc, Long: chartListDesc,
Hidden: !FeatureGateOCI.IsEnabled(), Hidden: !FeatureGateOCI.IsEnabled(),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return action.NewChartList(cfg).Run(out) return chartList.Run(out)
}, },
} }
f := cmd.Flags()
f.UintVar(&chartList.ColumnWidth, "max-col-width", 60, "maximum column width for output table")
return cmd
} }

@ -33,37 +33,55 @@ const bashCompDesc = `
Generate the autocompletion script for Helm for the bash shell. Generate the autocompletion script for Helm for the bash shell.
To load completions in your current shell session: To load completions in your current shell session:
$ source <(helm completion bash)
source <(helm completion bash)
To load completions for every new session, execute once: To load completions for every new session, execute once:
Linux: - Linux:
$ helm completion bash > /etc/bash_completion.d/helm
MacOS: helm completion bash > /etc/bash_completion.d/helm
$ helm completion bash > /usr/local/etc/bash_completion.d/helm
- MacOS:
helm completion bash > /usr/local/etc/bash_completion.d/helm
` `
const zshCompDesc = ` const zshCompDesc = `
Generate the autocompletion script for Helm for the zsh shell. Generate the autocompletion script for Helm for the zsh shell.
To load completions in your current shell session: To load completions in your current shell session:
$ source <(helm completion zsh)
source <(helm completion zsh)
To load completions for every new session, execute once: To load completions for every new session, execute once:
$ helm completion zsh > "${fpath[1]}/_helm"
helm completion zsh > "${fpath[1]}/_helm"
` `
const fishCompDesc = ` const fishCompDesc = `
Generate the autocompletion script for Helm for the fish shell. Generate the autocompletion script for Helm for the fish shell.
To load completions in your current shell session: To load completions in your current shell session:
$ helm completion fish | source
helm completion fish | source
To load completions for every new session, execute once: To load completions for every new session, execute once:
$ helm completion fish > ~/.config/fish/completions/helm.fish
helm completion fish > ~/.config/fish/completions/helm.fish
You will need to start a new shell for this setup to take effect. You will need to start a new shell for this setup to take effect.
` `
const powershellCompDesc = `
Generate the autocompletion script for powershell.
To load completions in your current shell session:
PS C:\> helm completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command
to your powershell profile.
`
const ( const (
noDescFlagName = "no-descriptions" noDescFlagName = "no-descriptions"
noDescFlagText = "disable completion descriptions" noDescFlagText = "disable completion descriptions"
@ -80,24 +98,23 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
} }
bash := &cobra.Command{ bash := &cobra.Command{
Use: "bash", Use: "bash",
Short: "generate autocompletion script for bash", Short: "generate autocompletion script for bash",
Long: bashCompDesc, Long: bashCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
DisableFlagsInUseLine: true, ValidArgsFunction: noCompletions,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionBash(out, cmd) return runCompletionBash(out, cmd)
}, },
} }
bash.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
zsh := &cobra.Command{ zsh := &cobra.Command{
Use: "zsh", Use: "zsh",
Short: "generate autocompletion script for zsh", Short: "generate autocompletion script for zsh",
Long: zshCompDesc, Long: zshCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
DisableFlagsInUseLine: true, ValidArgsFunction: noCompletions,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionZsh(out, cmd) return runCompletionZsh(out, cmd)
}, },
@ -105,25 +122,36 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
zsh.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText) zsh.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
fish := &cobra.Command{ fish := &cobra.Command{
Use: "fish", Use: "fish",
Short: "generate autocompletion script for fish", Short: "generate autocompletion script for fish",
Long: fishCompDesc, Long: fishCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
DisableFlagsInUseLine: true, ValidArgsFunction: noCompletions,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionFish(out, cmd) return runCompletionFish(out, cmd)
}, },
} }
fish.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText) fish.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
cmd.AddCommand(bash, zsh, fish) powershell := &cobra.Command{
Use: "powershell",
Short: "generate autocompletion script for powershell",
Long: powershellCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionPowershell(out, cmd)
},
}
powershell.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
cmd.AddCommand(bash, zsh, fish, powershell)
return cmd return cmd
} }
func runCompletionBash(out io.Writer, cmd *cobra.Command) error { func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
err := cmd.Root().GenBashCompletion(out) err := cmd.Root().GenBashCompletionV2(out, !disableCompDescriptions)
// In case the user renamed the helm binary (e.g., to be able to run // 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 // both helm2 and helm3), we hook the new binary name to the completion function
@ -174,6 +202,13 @@ func runCompletionFish(out io.Writer, cmd *cobra.Command) error {
return cmd.Root().GenFishCompletion(out, !disableCompDescriptions) return cmd.Root().GenFishCompletion(out, !disableCompDescriptions)
} }
func runCompletionPowershell(out io.Writer, cmd *cobra.Command) error {
if disableCompDescriptions {
return cmd.Root().GenPowerShellCompletion(out)
}
return cmd.Root().GenPowerShellCompletionWithDesc(out)
}
// Function to disable file completion // Function to disable file completion
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp

@ -29,9 +29,14 @@ import (
func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
storage := storageFixture() storage := storageFixture()
storage.Create(&release.Release{ storage.Create(&release.Release{
Name: "myrelease", Name: "myrelease",
Info: &release.Info{Status: release.StatusDeployed}, Info: &release.Info{Status: release.StatusDeployed},
Chart: &chart.Chart{}, Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Myrelease-Chart",
Version: "1.2.3",
},
},
Version: 1, Version: 1,
}) })
@ -57,3 +62,32 @@ func TestCompletionFileCompletion(t *testing.T) {
checkFileCompletion(t, "completion zsh", false) checkFileCompletion(t, "completion zsh", false)
checkFileCompletion(t, "completion fish", false) checkFileCompletion(t, "completion fish", false)
} }
func checkReleaseCompletion(t *testing.T, cmdName string, multiReleasesAllowed bool) {
multiReleaseTestGolden := "output/empty_nofile_comp.txt"
if multiReleasesAllowed {
multiReleaseTestGolden = "output/release_list_repeat_comp.txt"
}
tests := []cmdTestCase{{
name: "completion for uninstall",
cmd: fmt.Sprintf("__complete %s ''", cmdName),
golden: "output/release_list_comp.txt",
rels: []*release.Release{
release.Mock(&release.MockReleaseOptions{Name: "athos"}),
release.Mock(&release.MockReleaseOptions{Name: "porthos"}),
release.Mock(&release.MockReleaseOptions{Name: "aramis"}),
},
}, {
name: "completion for uninstall repetition",
cmd: fmt.Sprintf("__complete %s porthos ''", cmdName),
golden: multiReleaseTestGolden,
rels: []*release.Release{
release.Mock(&release.MockReleaseOptions{Name: "athos"}),
release.Mock(&release.MockReleaseOptions{Name: "porthos"}),
release.Mock(&release.MockReleaseOptions{Name: "aramis"}),
},
}}
for _, test := range tests {
runTestCmd(t, []cmdTestCase{test})
}
}

@ -100,7 +100,6 @@ func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func newDependencyListCmd(out io.Writer) *cobra.Command { func newDependencyListCmd(out io.Writer) *cobra.Command {
client := action.NewDependency() client := action.NewDependency()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list CHART", Use: "list CHART",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
@ -115,5 +114,9 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
return client.List(chartpath, out) return client.List(chartpath, out)
}, },
} }
f := cmd.Flags()
f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table")
return cmd return cmd
} }

@ -17,7 +17,6 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -150,7 +149,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
} }
} }
func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)() defer ensure.HelmHome(t)()
@ -188,7 +187,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
} }
// Make sure charts dir still has dependencies // Make sure charts dir still has dependencies
files, err := ioutil.ReadDir(filepath.Join(dir(chartname), "charts")) files, err := os.ReadDir(filepath.Join(dir(chartname), "charts"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -68,6 +68,17 @@ func newDocsCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)") f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)")
f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files") f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files")
cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
types := []string{"bash", "man", "markdown"}
var comps []string
for _, t := range types {
if strings.HasPrefix(t, toComplete) {
comps = append(comps, t)
}
}
return comps, cobra.ShellCompDirectiveNoFileComp
})
return cmd return cmd
} }

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

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"log" "log"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -46,7 +47,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
} }
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.Version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.StringVar(&c.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used")
f.BoolVar(&c.Verify, "verify", false, "verify the package before using it") f.BoolVar(&c.Verify, "verify", false, "verify the package before using it")
f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification") f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart") f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart")
@ -56,6 +57,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
} }
// bindOutputFlag will add the output flag to the given command and bind the // bindOutputFlag will add the output flag to the given command and bind the
@ -66,11 +68,14 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var formatNames []string var formatNames []string
for _, format := range output.Formats() { for format, desc := range output.FormatsWithDesc() {
if strings.HasPrefix(format, toComplete) { if strings.HasPrefix(format, toComplete) {
formatNames = append(formatNames, format) formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
} }
} }
// Sort the results to get a deterministic order for the tests
sort.Strings(formatNames)
return formatNames, cobra.ShellCompDirectiveNoFileComp return formatNames, cobra.ShellCompDirectiveNoFileComp
}) })
@ -150,7 +155,21 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
for _, details := range indexFile.Entries[chartName] { for _, details := range indexFile.Entries[chartName] {
version := details.Metadata.Version version := details.Metadata.Version
if strings.HasPrefix(version, toComplete) { if strings.HasPrefix(version, toComplete) {
versions = append(versions, version) appVersion := details.Metadata.AppVersion
appVersionDesc := ""
if appVersion != "" {
appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
}
created := details.Created.Format("January 2, 2006")
createdDesc := ""
if created != "" {
createdDesc = fmt.Sprintf("Created: %s ", created)
}
deprecated := ""
if details.Metadata.Deprecated {
deprecated = "(deprecated)"
}
versions = append(versions, fmt.Sprintf("%s\t%s%s%s", version, appVersionDesc, createdDesc, deprecated))
} }
} }
} }

@ -45,7 +45,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])

@ -42,6 +42,10 @@ func TestGetCmd(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestGetAllCompletion(t *testing.T) {
checkReleaseCompletion(t, "get all", false)
}
func TestGetAllRevisionCompletion(t *testing.T) { func TestGetAllRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get all") revisionFlagCompletionTest(t, "get all")
} }

@ -45,7 +45,7 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])

@ -37,6 +37,10 @@ func TestGetHooks(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestGetHooksCompletion(t *testing.T) {
checkReleaseCompletion(t, "get hooks", false)
}
func TestGetHooksRevisionCompletion(t *testing.T) { func TestGetHooksRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get hooks") revisionFlagCompletionTest(t, "get hooks")
} }

@ -47,7 +47,7 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])

@ -37,6 +37,10 @@ func TestGetManifest(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestGetManifestCompletion(t *testing.T) {
checkReleaseCompletion(t, "get manifest", false)
}
func TestGetManifestRevisionCompletion(t *testing.T) { func TestGetManifestRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get manifest") revisionFlagCompletionTest(t, "get manifest")
} }

@ -43,7 +43,7 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])

@ -37,6 +37,10 @@ func TestGetNotesCmd(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestGetNotesCompletion(t *testing.T) {
checkReleaseCompletion(t, "get notes", false)
}
func TestGetNotesRevisionCompletion(t *testing.T) { func TestGetNotesRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get notes") revisionFlagCompletionTest(t, "get notes")
} }

@ -50,7 +50,7 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
vals, err := client.Run(args[0]) vals, err := client.Run(args[0])

@ -53,6 +53,10 @@ func TestGetValuesCmd(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestGetValuesCompletion(t *testing.T) {
checkReleaseCompletion(t, "get values", false)
}
func TestGetValuesRevisionCompletion(t *testing.T) { func TestGetValuesRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get values") revisionFlagCompletionTest(t, "get values")
} }

@ -32,6 +32,7 @@ import (
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/gates" "helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
@ -59,6 +60,12 @@ func warning(format string, v ...interface{}) {
} }
func main() { func main() {
// Setting the name of the app for managedFields in the Kubernetes client.
// It is set here to the full name of "helm" so that renaming of helm to
// another name (e.g., helm2 or helm3) does not change the name of the
// manager as picked up by the automated name detection.
kube.ManagedFieldsManager = "helm"
actionConfig := new(action.Configuration) actionConfig := new(action.Configuration)
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil { if err != nil {

@ -65,7 +65,7 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
history, err := getHistory(client, args[0]) history, err := getHistory(client, args[0])
@ -193,7 +193,9 @@ func compListRevisions(toComplete string, cfg *action.Configuration, releaseName
for _, release := range hist { for _, release := range hist {
version := strconv.Itoa(release.Version) version := strconv.Itoa(release.Version)
if strings.HasPrefix(version, toComplete) { if strings.HasPrefix(version, toComplete) {
revisions = append(revisions, version) appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", version, appVersion, chartDesc))
} }
} }
return revisions, cobra.ShellCompDirectiveNoFileComp return revisions, cobra.ShellCompDirectiveNoFileComp

@ -109,6 +109,10 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestHistoryCompletion(t *testing.T) {
checkReleaseCompletion(t, "history", false)
}
func TestHistoryFileCompletion(t *testing.T) { func TestHistoryFileCompletion(t *testing.T) {
checkFileCompletion(t, "history", false) checkFileCompletion(t, "history", false)
checkFileCompletion(t, "history myrelease", false) checkFileCompletion(t, "history myrelease", false)

@ -145,7 +145,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema")
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")

@ -18,10 +18,39 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo/repotest"
) )
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
}))
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
}))
defer srv2.Close()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
repoFile := filepath.Join(srv.Root(), "repositories.yaml")
tests := []cmdTestCase{ tests := []cmdTestCase{
// Install, base case // Install, base case
{ {
@ -207,6 +236,22 @@ func TestInstall(t *testing.T) {
name: "install chart with only crds", name: "install chart with only crds",
cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default", cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default",
}, },
// Verify the user/pass works
{
name: "basic install with credentials",
cmd: "install aeneas reqtest --namespace default --repo " + srv.URL() + " --username username --password password",
golden: "output/install.txt",
},
{
name: "basic install with credentials",
cmd: "install aeneas reqtest --namespace default --repo " + srv2.URL + " --username username --password password --pass-credentials",
golden: "output/install.txt",
},
{
name: "basic install with credentials and no repo",
cmd: fmt.Sprintf("install aeneas test/reqtest --username username --password password --repository-config %s --repository-cache %s", repoFile, srv.Root()),
golden: "output/install.txt",
},
} }
runTestActionCmd(t, tests) runTestActionCmd(t, tests)

@ -114,7 +114,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format") f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
f.StringVar(&client.TimeFormat, "time-format", "", "format time. Example: --time-format 2009-11-17 20:34:10 +0000 UTC") f.StringVar(&client.TimeFormat, "time-format", "", `format time using golang time formatter. Example: --time-format "2006-01-02 15:04:05Z0700"`)
f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date") f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order") f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
f.BoolVarP(&client.All, "all", "a", false, "show all releases without any filter applied") f.BoolVarP(&client.All, "all", "a", false, "show all releases without any filter applied")
@ -126,7 +126,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Pending, "pending", false, "show pending releases") f.BoolVar(&client.Pending, "pending", false, "show pending releases")
f.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces") f.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces")
f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch") f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value") f.IntVar(&client.Offset, "offset", 0, "next release index in the list, used to offset from start value")
f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results") f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.") f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.")
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)
@ -193,8 +193,32 @@ func (r *releaseListWriter) WriteYAML(out io.Writer) error {
return output.EncodeYAML(out, r.releases) return output.EncodeYAML(out, r.releases)
} }
// Returns all releases from 'releases', except those with names matching 'ignoredReleases'
func filterReleases(releases []*release.Release, ignoredReleaseNames []string) []*release.Release {
// if ignoredReleaseNames is nil, just return releases
if ignoredReleaseNames == nil {
return releases
}
var filteredReleases []*release.Release
for _, rel := range releases {
found := false
for _, ignoredName := range ignoredReleaseNames {
if rel.Name == ignoredName {
found = true
break
}
}
if !found {
filteredReleases = append(filteredReleases, rel)
}
}
return filteredReleases
}
// Provide dynamic auto-completion for release names // Provide dynamic auto-completion for release names
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) { func compListReleases(toComplete string, ignoredReleaseNames []string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug) cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug)
client := action.NewList(cfg) client := action.NewList(cfg)
@ -203,14 +227,16 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
client.Filter = fmt.Sprintf("^%s", toComplete) client.Filter = fmt.Sprintf("^%s", toComplete)
client.SetStateMask() client.SetStateMask()
results, err := client.Run() releases, err := client.Run()
if err != nil { if err != nil {
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
} }
var choices []string var choices []string
for _, res := range results { filteredReleases := filterReleases(releases, ignoredReleaseNames)
choices = append(choices, res.Name) for _, rel := range filteredReleases {
choices = append(choices,
fmt.Sprintf("%s\t%s-%s -> %s", rel.Name, rel.Chart.Metadata.Name, rel.Chart.Metadata.Version, rel.Info.Status.String()))
} }
return choices, cobra.ShellCompDirectiveNoFileComp return choices, cobra.ShellCompDirectiveNoFileComp

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

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

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

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

@ -18,6 +18,8 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -250,6 +252,115 @@ func TestPullCmd(t *testing.T) {
} }
} }
func TestPullWithCredentialsCmd(t *testing.T) {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
}
}))
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
}))
defer srv2.Close()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
// all flags will get "-d outdir" appended.
tests := []struct {
name string
args string
existFile string
existDir string
wantError bool
wantErrorMsg string
expectFile string
expectDir bool
}{
{
name: "Chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz",
args: "signtest --repo " + srv.URL() + " --username username --password password",
},
{
name: "Fail fetching non-existent chart on repo URL",
args: "someChart --repo " + srv.URL() + " --username username --password password",
wantError: true,
},
{
name: "Specific version chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz",
args: "signtest --version=0.1.0 --repo " + srv.URL() + " --username username --password password",
},
{
name: "Specific version chart fetch using repo URL",
args: "signtest --version=0.2.0 --repo " + srv.URL() + " --username username --password password",
wantError: true,
},
{
name: "Chart located on different domain with credentials passed",
args: "reqtest --repo " + srv2.URL + " --username username --password password --pass-credentials",
expectFile: "./reqtest-0.1.0.tgz",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
outdir := srv.Root()
cmd := fmt.Sprintf("pull %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)
}
}
if tt.existDir != "" {
file := filepath.Join(outdir, tt.existDir)
err := os.Mkdir(file, 0755)
if err != nil {
t.Fatal(err)
}
}
_, _, err := executeActionCommand(cmd)
if err != nil {
if tt.wantError {
if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() {
t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg)
}
return
}
t.Fatalf("%q reported error: %s", tt.name, err)
}
ef := filepath.Join(outdir, tt.expectFile)
fi, err := os.Stat(ef)
if err != nil {
t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err)
}
if fi.IsDir() != tt.expectDir {
t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir)
}
})
}
}
func TestPullVersionCompletion(t *testing.T) { func TestPullVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml" repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository" repoCache := "testdata/helmhome/helm/repository"

@ -67,7 +67,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
return cmd return cmd
} }
// Adapted from https://github.com/deislabs/oras // Adapted from https://oras.land/oras-go
func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) {
var err error var err error
username := usernameOpt username := usernameOpt
@ -110,7 +110,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
return username, password, nil return username, password, nil
} }
// Copied/adapted from https://github.com/deislabs/oras // Copied/adapted from https://oras.land/oras-go
func readLine(prompt string, silent bool) (string, error) { func readLine(prompt string, silent bool) (string, error) {
fmt.Print(prompt) fmt.Print(prompt)
if silent { if silent {

@ -52,7 +52,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()

@ -20,6 +20,10 @@ import (
"testing" "testing"
) )
func TestReleaseTestingCompletion(t *testing.T) {
checkReleaseCompletion(t, "test", false)
}
func TestReleaseTestingFileCompletion(t *testing.T) { func TestReleaseTestingFileCompletion(t *testing.T) {
checkFileCompletion(t, "test", false) checkFileCompletion(t, "test", false)
checkFileCompletion(t, "test myrelease", false) checkFileCompletion(t, "test myrelease", false)

@ -29,7 +29,7 @@ import (
"github.com/gofrs/flock" "github.com/gofrs/flock"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
@ -48,6 +48,8 @@ type repoAddOptions struct {
url string url string
username string username string
password string password string
passwordFromStdinOpt bool
passCredentialsAll bool
forceUpdate bool forceUpdate bool
allowDeprecatedRepos bool allowDeprecatedRepos bool
@ -84,6 +86,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.username, "username", "", "chart repository username") f.StringVar(&o.username, "username", "", "chart repository username")
f.StringVar(&o.password, "password", "", "chart repository password") f.StringVar(&o.password, "password", "", "chart repository password")
f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read chart repository password from stdin")
f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists") f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists")
f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.") f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.")
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
@ -91,6 +94,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior") 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")
f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
return cmd return cmd
} }
@ -112,7 +116,14 @@ func (o *repoAddOptions) run(out io.Writer) error {
} }
// Acquire a file lock for process synchronization // Acquire a file lock for process synchronization
fileLock := flock.New(strings.Replace(o.repoFile, filepath.Ext(o.repoFile), ".lock", 1)) repoFileExt := filepath.Ext(o.repoFile)
var lockPath string
if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) {
lockPath = strings.Replace(o.repoFile, repoFileExt, ".lock", 1)
} else {
lockPath = o.repoFile + ".lock"
}
fileLock := flock.New(lockPath)
lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
locked, err := fileLock.TryLockContext(lockCtx, time.Second) locked, err := fileLock.TryLockContext(lockCtx, time.Second)
@ -134,14 +145,24 @@ func (o *repoAddOptions) run(out io.Writer) error {
} }
if o.username != "" && o.password == "" { if o.username != "" && o.password == "" {
fd := int(os.Stdin.Fd()) if o.passwordFromStdinOpt {
fmt.Fprint(out, "Password: ") passwordFromStdin, err := io.ReadAll(os.Stdin)
password, err := terminal.ReadPassword(fd) if err != nil {
fmt.Fprintln(out) return err
if err != nil { }
return err password := strings.TrimSuffix(string(passwordFromStdin), "\n")
password = strings.TrimSuffix(password, "\r")
o.password = password
} else {
fd := int(os.Stdin.Fd())
fmt.Fprint(out, "Password: ")
password, err := term.ReadPassword(fd)
fmt.Fprintln(out)
if err != nil {
return err
}
o.password = string(password)
} }
o.password = string(password)
} }
c := repo.Entry{ c := repo.Entry{
@ -149,6 +170,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
URL: o.url, URL: o.url,
Username: o.username, Username: o.username,
Password: o.password, Password: o.password,
PassCredentialsAll: o.passCredentialsAll,
CertFile: o.certFile, CertFile: o.certFile,
KeyFile: o.keyFile, KeyFile: o.keyFile,
CAFile: o.caFile, CAFile: o.caFile,

@ -21,6 +21,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"testing" "testing"
@ -142,6 +143,18 @@ func TestRepoAddConcurrentDirNotExist(t *testing.T) {
repoAddConcurrent(t, testName, repoFile) repoAddConcurrent(t, testName, repoFile)
} }
func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
const testName = "test-name-3"
repoFile := filepath.Join(ensure.TempDir(t), "repositories")
repoAddConcurrent(t, testName, repoFile)
}
func TestRepoAddConcurrentHiddenFile(t *testing.T) {
const testName = "test-name-4"
repoFile := filepath.Join(ensure.TempDir(t), ".repositories")
repoAddConcurrent(t, testName, repoFile)
}
func repoAddConcurrent(t *testing.T, testName, repoFile string) { func repoAddConcurrent(t *testing.T, testName, repoFile string) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil { if err != nil {
@ -192,3 +205,33 @@ func TestRepoAddFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo add reponame", false) checkFileCompletion(t, "repo add reponame", false)
checkFileCompletion(t, "repo add reponame https://example.com", false) checkFileCompletion(t, "repo add reponame https://example.com", false)
} }
func TestRepoAddWithPasswordFromStdin(t *testing.T) {
srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/testserver/*.*")
defer srv.Stop()
defer resetEnv()()
in, err := os.Open("testdata/password")
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
tmpdir := ensure.TempDir(t)
repoFile := filepath.Join(tmpdir, "repositories.yaml")
store := storageFixture()
const testName = "test-name"
const username = "username"
cmd := fmt.Sprintf("repo add %s %s --repository-config %s --repository-cache %s --username %s --password-stdin", testName, srv.URL(), repoFile, tmpdir, username)
var result string
_, result, err = executeActionCommandStdinC(store, in, cmd)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
if !strings.Contains(result, fmt.Sprintf("\"%s\" has been added to your repositories", testName)) {
t.Errorf("Repo was not successfully added. Output: %s", result)
}
}

@ -17,6 +17,7 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"strings" "strings"
@ -131,7 +132,7 @@ func compListRepos(prefix string, ignoredRepoNames []string) []string {
filteredRepos := filterRepos(f.Repositories, ignoredRepoNames) filteredRepos := filterRepos(f.Repositories, ignoredRepoNames)
for _, repo := range filteredRepos { for _, repo := range filteredRepos {
if strings.HasPrefix(repo.Name, prefix) { if strings.HasPrefix(repo.Name, prefix) {
rNames = append(rNames, repo.Name) rNames = append(rNames, fmt.Sprintf("%s\t%s", repo.Name, repo.URL))
} }
} }
} }

@ -18,6 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -161,6 +162,51 @@ func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string,
} }
} }
func TestRepoRemoveCompletion(t *testing.T) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer ts.Stop()
rootDir := ensure.TempDir(t)
repoFile := filepath.Join(rootDir, "repositories.yaml")
repoCache := filepath.Join(rootDir, "cache/")
var testRepoNames = []string{"foo", "bar", "baz"}
// Add test repos
for _, repoName := range testRepoNames {
o := &repoAddOptions{
name: repoName,
url: ts.URL(),
repoFile: repoFile,
}
if err := o.run(os.Stderr); err != nil {
t.Error(err)
}
}
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
// In the following tests, we turn off descriptions for completions by using __completeNoDesc.
// We have to do this because the description will contain the port used by the webserver,
// and that port changes each time we run the test.
tests := []cmdTestCase{{
name: "completion for repo remove",
cmd: fmt.Sprintf("%s __completeNoDesc repo remove ''", repoSetup),
golden: "output/repo_list_comp.txt",
}, {
name: "completion for repo remove repetition",
cmd: fmt.Sprintf("%s __completeNoDesc repo remove foo ''", repoSetup),
golden: "output/repo_repeat_comp.txt",
}}
for _, test := range tests {
runTestCmd(t, []cmdTestCase{test})
}
}
func TestRepoRemoveFileCompletion(t *testing.T) { func TestRepoRemoveFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo remove", false) checkFileCompletion(t, "repo remove", false)
checkFileCompletion(t, "repo remove repo1", false) checkFileCompletion(t, "repo remove repo1", false)

@ -32,6 +32,10 @@ import (
const updateDesc = ` const updateDesc = `
Update gets the latest information about charts from the respective chart repositories. Update gets the latest information about charts from the respective chart repositories.
Information is cached locally, where it is used by commands like 'helm search'. Information is cached locally, where it is used by commands like 'helm search'.
You can optionally specify a list of repositories you want to update.
$ helm repo update <repo_name> ...
To update all the repositories, use 'helm repo update'.
` `
var errNoRepositories = errors.New("no repositories found. You must add one before updating") var errNoRepositories = errors.New("no repositories found. You must add one before updating")
@ -40,21 +44,25 @@ type repoUpdateOptions struct {
update func([]*repo.ChartRepository, io.Writer) update func([]*repo.ChartRepository, io.Writer)
repoFile string repoFile string
repoCache string repoCache string
names []string
} }
func newRepoUpdateCmd(out io.Writer) *cobra.Command { func newRepoUpdateCmd(out io.Writer) *cobra.Command {
o := &repoUpdateOptions{update: updateCharts} o := &repoUpdateOptions{update: updateCharts}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "update", Use: "update [REPO1 [REPO2 ...]]",
Aliases: []string{"up"}, Aliases: []string{"up"},
Short: "update information of available charts locally from chart repositories", Short: "update information of available charts locally from chart repositories",
Long: updateDesc, Long: updateDesc,
Args: require.NoArgs, Args: require.MinimumNArgs(0),
ValidArgsFunction: noCompletions, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache o.repoCache = settings.RepositoryCache
o.names = args
return o.run(out) return o.run(out)
}, },
} }
@ -63,19 +71,36 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
func (o *repoUpdateOptions) run(out io.Writer) error { func (o *repoUpdateOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.repoFile) f, err := repo.LoadFile(o.repoFile)
if isNotExist(err) || len(f.Repositories) == 0 { switch {
case isNotExist(err):
return errNoRepositories
case err != nil:
return errors.Wrapf(err, "failed loading file: %s", o.repoFile)
case len(f.Repositories) == 0:
return errNoRepositories return errNoRepositories
} }
var repos []*repo.ChartRepository var repos []*repo.ChartRepository
for _, cfg := range f.Repositories { updateAllRepos := len(o.names) == 0
r, err := repo.NewChartRepository(cfg, getter.All(settings))
if err != nil { if !updateAllRepos {
// Fail early if the user specified an invalid repo to update
if err := checkRequestedRepos(o.names, f.Repositories); err != nil {
return err return err
} }
if o.repoCache != "" { }
r.CachePath = o.repoCache
for _, cfg := range f.Repositories {
if updateAllRepos || isRepoRequested(cfg.Name, o.names) {
r, err := repo.NewChartRepository(cfg, getter.All(settings))
if err != nil {
return err
}
if o.repoCache != "" {
r.CachePath = o.repoCache
}
repos = append(repos, r)
} }
repos = append(repos, r)
} }
o.update(repos, out) o.update(repos, out)
@ -99,3 +124,28 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) {
wg.Wait() wg.Wait()
fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈") fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈")
} }
func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) error {
for _, requestedRepo := range requestedRepos {
found := false
for _, repo := range validRepos {
if requestedRepo == repo.Name {
found = true
break
}
}
if !found {
return errors.Errorf("no repositories found matching '%s'. Nothing will be updated", requestedRepo)
}
}
return nil
}
func isRepoRequested(repoName string, requestedRepos []string) bool {
for _, requestedRepo := range requestedRepos {
if repoName == requestedRepo {
return true
}
}
return false
}

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -47,26 +48,79 @@ func TestUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if got := out.String(); !strings.Contains(got, "charts") { if got := out.String(); !strings.Contains(got, "charts") ||
t.Errorf("Expected 'charts' got %q", got) !strings.Contains(got, "firstexample") ||
!strings.Contains(got, "secondexample") {
t.Errorf("Expected 'charts', 'firstexample' and 'secondexample' but got %q", got)
} }
} }
func TestUpdateCustomCacheCmd(t *testing.T) { func TestUpdateCmdMultiple(t *testing.T) {
var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer) {
for _, re := range repos {
fmt.Fprintln(out, re.Config.Name)
}
}
o := &repoUpdateOptions{
update: updater,
repoFile: "testdata/repositories.yaml",
names: []string{"firstexample", "charts"},
}
if err := o.run(&out); err != nil {
t.Fatal(err)
}
if got := out.String(); !strings.Contains(got, "charts") ||
!strings.Contains(got, "firstexample") ||
strings.Contains(got, "secondexample") {
t.Errorf("Expected 'charts' and 'firstexample' but not 'secondexample' but got %q", got)
}
}
func TestUpdateCmdInvalid(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer) {
for _, re := range repos {
fmt.Fprintln(out, re.Config.Name)
}
}
o := &repoUpdateOptions{
update: updater,
repoFile: "testdata/repositories.yaml",
names: []string{"firstexample", "invalid"},
}
if err := o.run(&out); err == nil {
t.Fatal("expected error but did not get one")
}
}
func TestUpdateCustomCacheCmd(t *testing.T) {
rootDir := ensure.TempDir(t) rootDir := ensure.TempDir(t)
cachePath := filepath.Join(rootDir, "updcustomcache") cachePath := filepath.Join(rootDir, "updcustomcache")
_ = os.Mkdir(cachePath, os.ModePerm) os.Mkdir(cachePath, os.ModePerm)
defer os.RemoveAll(cachePath) defer os.RemoveAll(cachePath)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer ts.Stop()
o := &repoUpdateOptions{ o := &repoUpdateOptions{
update: updateCharts, update: updateCharts,
repoFile: "testdata/repositories.yaml", repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
repoCache: cachePath, repoCache: cachePath,
} }
if err := o.run(&out); err != nil { b := ioutil.Discard
if err := o.run(b); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(filepath.Join(cachePath, "charts-index.yaml")); err != nil { if _, err := os.Stat(filepath.Join(cachePath, "test-index.yaml")); err != nil {
t.Fatalf("error finding created index file in custom cache: %v", err) t.Fatalf("error finding created index file in custom cache: %v", err)
} }
} }
@ -103,4 +157,5 @@ func TestUpdateCharts(t *testing.T) {
func TestRepoUpdateFileCompletion(t *testing.T) { func TestRepoUpdateFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo update", false) checkFileCompletion(t, "repo update", false)
checkFileCompletion(t, "repo update repo1", false)
} }

@ -48,7 +48,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
} }
if len(args) == 1 { if len(args) == 1 {

@ -98,7 +98,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Setup shell completion for the namespace flag // Setup shell completion for the namespace flag
err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if client, err := actionConfig.KubernetesClientSet(); err == nil { if client, err := actionConfig.KubernetesClientSet(); err == nil {
// Choose a long enough timeout that the user notices somethings is not working // Choose a long enough timeout that the user notices something is not working
// but short enough that the user is not made to wait very long // but short enough that the user is not made to wait very long
to := int64(3) to := int64(3)
cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug) cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug)
@ -131,13 +131,13 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules, loadingRules,
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil { &clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
ctxs := []string{} comps := []string{}
for name := range config.Contexts { for name, context := range config.Contexts {
if strings.HasPrefix(name, toComplete) { if strings.HasPrefix(name, toComplete) {
ctxs = append(ctxs, name) comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
} }
} }
return ctxs, cobra.ShellCompDirectiveNoFileComp return comps, cobra.ShellCompDirectiveNoFileComp
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })

@ -27,7 +27,7 @@ import (
func checkPerms() { func checkPerms() {
// This function MUST NOT FAIL, as it is just a check for a common permissions problem. // This function MUST NOT FAIL, as it is just a check for a common permissions problem.
// If for some reason the function hits a stopping condition, it may panic. But only if // If for some reason the function hits a stopping condition, it may panic. But only if
// we can be sure that it is panicing because Helm cannot proceed. // we can be sure that it is panicking because Helm cannot proceed.
kc := settings.KubeConfig kc := settings.KubeConfig
if kc == "" { if kc == "" {

@ -88,5 +88,5 @@ func TestSearchHubOutputCompletion(t *testing.T) {
} }
func TestSearchHubFileCompletion(t *testing.T) { func TestSearchHubFileCompletion(t *testing.T) {
checkFileCompletion(t, "search hub", true) // File completion may be useful when inputing a keyword checkFileCompletion(t, "search hub", true) // File completion may be useful when inputting a keyword
} }

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -143,7 +144,7 @@ func (o *searchRepoOptions) setupSearchedVersion() {
} }
func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) { func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) {
if len(o.version) == 0 { if o.version == "" {
return res, nil return res, nil
} }
@ -154,26 +155,19 @@ func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Res
data := res[:0] data := res[:0]
foundNames := map[string]bool{} foundNames := map[string]bool{}
appendSearchResults := func(res *search.Result) {
data = append(data, res)
if !o.versions {
foundNames[res.Name] = true // If user hasn't requested all versions, only show the latest that matches
}
}
for _, r := range res { for _, r := range res {
if _, found := foundNames[r.Name]; found { // if not returning all versions and already have found a result,
// you're done!
if !o.versions && foundNames[r.Name] {
continue continue
} }
v, err := semver.NewVersion(r.Chart.Version) v, err := semver.NewVersion(r.Chart.Version)
if err != nil { if err != nil {
// If the current version number check appears ErrSegmentStartsZero or ErrInvalidPrerelease error and not devel mode, ignore continue
if (err == semver.ErrSegmentStartsZero || err == semver.ErrInvalidPrerelease) && !o.devel { }
continue if constraint.Check(v) {
} data = append(data, r)
appendSearchResults(r) foundNames[r.Name] = true
} else if constraint.Check(v) {
appendSearchResults(r)
} }
} }
@ -194,6 +188,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) {
ind, err := repo.LoadIndexFile(f) ind, err := repo.LoadIndexFile(f)
if err != nil { if err != nil {
warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n) warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n)
warning("%s", err)
continue continue
} }
@ -307,7 +302,14 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
// First check completions for repos // First check completions for repos
repos := compListRepos("", nil) repos := compListRepos("", nil)
for _, repo := range repos { for _, repoInfo := range repos {
// Split name from description
repoInfo := strings.Split(repoInfo, "\t")
repo := repoInfo[0]
repoDesc := ""
if len(repoInfo) > 1 {
repoDesc = repoInfo[1]
}
repoWithSlash := fmt.Sprintf("%s/", repo) repoWithSlash := fmt.Sprintf("%s/", repo)
if strings.HasPrefix(toComplete, repoWithSlash) { if strings.HasPrefix(toComplete, repoWithSlash) {
// Must complete with charts within the specified repo // Must complete with charts within the specified repo
@ -315,15 +317,15 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
noSpace = false noSpace = false
break break
} else if strings.HasPrefix(repo, toComplete) { } else if strings.HasPrefix(repo, toComplete) {
// Must complete the repo name // Must complete the repo name with the slash, followed by the description
completions = append(completions, repoWithSlash) completions = append(completions, fmt.Sprintf("%s\t%s", repoWithSlash, repoDesc))
noSpace = true noSpace = true
} }
} }
cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug) cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
// Now handle completions for url prefixes // Now handle completions for url prefixes
for _, url := range []string{"https://", "http://", "file://"} { for _, url := range []string{"https://\tChart URL prefix", "http://\tChart URL prefix", "file://\tChart local URL prefix"} {
if strings.HasPrefix(toComplete, url) { if strings.HasPrefix(toComplete, url) {
// The user already put in the full url prefix; we don't have // The user already put in the full url prefix; we don't have
// anything to add, but make sure the shell does not default // anything to add, but make sure the shell does not default
@ -346,7 +348,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
// listing the entire content of the current directory which will // listing the entire content of the current directory which will
// be too many choices for the user to find the real repos) // be too many choices for the user to find the real repos)
if includeFiles && len(completions) > 0 && len(toComplete) > 0 { if includeFiles && len(completions) > 0 && len(toComplete) > 0 {
if files, err := ioutil.ReadDir("."); err == nil { if files, err := os.ReadDir("."); err == nil {
for _, file := range files { for _, file := range files {
if strings.HasPrefix(file.Name(), toComplete) { if strings.HasPrefix(file.Name(), toComplete) {
// We are completing a file prefix // We are completing a file prefix
@ -360,7 +362,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
// If the user didn't provide any input to completion, // If the user didn't provide any input to completion,
// we provide a hint that a path can also be used // we provide a hint that a path can also be used
if includeFiles && len(toComplete) == 0 { if includeFiles && len(toComplete) == 0 {
completions = append(completions, "./", "/") completions = append(completions, "./\tRelative path prefix to local chart", "/\tAbsolute path prefix to local chart")
} }
cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug) cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug)

@ -68,14 +68,6 @@ func TestSearchRepositoriesCmd(t *testing.T) {
name: "search for 'maria', expect valid json output", name: "search for 'maria', expect valid json output",
cmd: "search repo maria --output json", cmd: "search repo maria --output json",
golden: "output/search-output-json.txt", golden: "output/search-output-json.txt",
}, {
name: "search for 'maria', expect one match with semver begin with zero development version",
cmd: "search repo maria --devel",
golden: "output/search-semver-pre-zero-devel-release.txt",
}, {
name: "search for 'nginx-ingress', expect one match with invalid development pre version",
cmd: "search repo nginx-ingress --devel",
golden: "output/search-semver-pre-invalid-release.txt",
}, { }, {
name: "search for 'alpine', expect valid yaml output", name: "search for 'alpine', expect valid yaml output",
cmd: "search repo alpine --output yaml", cmd: "search repo alpine --output yaml",
@ -97,5 +89,5 @@ func TestSearchRepoOutputCompletion(t *testing.T) {
} }
func TestSearchRepoFileCompletion(t *testing.T) { func TestSearchRepoFileCompletion(t *testing.T) {
checkFileCompletion(t, "search repo", true) // File completion may be useful when inputing a keyword checkFileCompletion(t, "search repo", true) // File completion may be useful when inputting a keyword
} }

@ -59,7 +59,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListReleases(toComplete, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
rel, err := client.Run(args[0]) rel, err := client.Run(args[0])

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

@ -52,6 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var skipTests bool var skipTests bool
client := action.NewInstall(cfg) client := action.NewInstall(cfg)
valueOpts := &values.Options{} valueOpts := &values.Options{}
var kubeVersion string
var extraAPIs []string var extraAPIs []string
var showFiles []string var showFiles []string
@ -64,6 +65,14 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
if kubeVersion != "" {
parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion)
if err != nil {
return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err)
}
client.KubeVersion = parsedKubeVersion
}
client.DryRun = true client.DryRun = true
client.ReleaseName = "RELEASE-NAME" client.ReleaseName = "RELEASE-NAME"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
@ -171,6 +180,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output") f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output")
f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output") f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output")
f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall") f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.") f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
bindPostRenderFlag(cmd, &client.PostRenderer) bindPostRenderFlag(cmd, &client.PostRenderer)
@ -188,7 +198,7 @@ func isTestHook(h *release.Hook) bool {
} }
// The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile) // The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile)
// are coppied from the actions package. This is part of a change to correct a // are copied from the actions package. This is part of a change to correct a
// bug introduced by #8156. As part of the todo to refactor renderResources // bug introduced by #8156. As part of the todo to refactor renderResources
// this duplicate code should be removed. It is added here so that the API // this duplicate code should be removed. It is added here so that the API
// surface area is as minimally impacted as possible in fixing the issue. // surface area is as minimally impacted as possible in fixing the issue.

@ -74,6 +74,11 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-with-template-lib-archive-dep"), cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-with-template-lib-archive-dep"),
golden: "output/template-chart-with-template-lib-archive-dep.txt", golden: "output/template-chart-with-template-lib-archive-dep.txt",
}, },
{
name: "check kube version",
cmd: fmt.Sprintf("template --kube-version 1.16.0 '%s'", chartPath),
golden: "output/template-with-kube-version.txt",
},
{ {
name: "check kube api versions", name: "check kube api versions",
cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath), cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath),

@ -7,8 +7,7 @@ echo "Args received: ${@}"
# Final printout is the optional completion directive of the form :<directive> # Final printout is the optional completion directive of the form :<directive>
if [ "$HELM_NAMESPACE" = "default" ]; then if [ "$HELM_NAMESPACE" = "default" ]; then
# Output an invalid directive, which should be ignored echo ":0"
echo ":2222"
# else # else
# Don't include the directive, to test it is really optional # Don't include the directive, to test it is really optional
fi fi

@ -4,6 +4,8 @@ entries:
- name: alpine - name: alpine
url: https://charts.helm.sh/stable/alpine-0.1.0.tgz url: https://charts.helm.sh/stable/alpine-0.1.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2018-06-27T10:00:18.230700509Z"
deprecated: true
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
@ -13,9 +15,11 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
- name: alpine - name: alpine
url: https://charts.helm.sh/stable/alpine-0.2.0.tgz url: https://charts.helm.sh/stable/alpine-0.2.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2018-07-09T11:34:37.797864902Z"
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
@ -25,9 +29,11 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
- name: alpine - name: alpine
url: https://charts.helm.sh/stable/alpine-0.3.0-rc.1.tgz url: https://charts.helm.sh/stable/alpine-0.3.0-rc.1.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2020-11-12T08:44:58.872726222Z"
home: https://helm.sh/helm home: https://helm.sh/helm
sources: sources:
- https://github.com/helm/helm - https://github.com/helm/helm
@ -37,10 +43,12 @@ entries:
keywords: [] keywords: []
maintainers: [] maintainers: []
icon: "" icon: ""
apiVersion: v2
mariadb: mariadb:
- name: mariadb - name: mariadb
url: https://charts.helm.sh/stable/mariadb-0.3.0.tgz url: https://charts.helm.sh/stable/mariadb-0.3.0.tgz
checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56 checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
created: "2018-04-23T08:20:27.160959131Z"
home: https://mariadb.org home: https://mariadb.org
sources: sources:
- https://github.com/bitnami/bitnami-docker-mariadb - https://github.com/bitnami/bitnami-docker-mariadb
@ -55,33 +63,4 @@ entries:
- name: Bitnami - name: Bitnami
email: containers@bitnami.com email: containers@bitnami.com
icon: "" icon: ""
- name: mariadb apiVersion: v2
url: https://charts.helm.sh/stable/mariadb-0.3.0-0565674.tgz
checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
home: https://mariadb.org
sources:
- https://github.com/bitnami/bitnami-docker-mariadb
version: 0.3.0-0565674
description: Chart for MariaDB
keywords:
- mariadb
- mysql
- database
- sql
maintainers:
- name: Bitnami
email: containers@bitnami.com
icon: ""
nginx-ingress:
- name: nginx-ingress
url: https://github.com/kubernetes/ingress-nginx/ingress-a.b.c.sdfsdf.tgz
checksum: 25229f6de44a2be9f215d11dbff31167ddc8ba56
home: https://github.com/kubernetes/ingress-nginx
sources:
- https://github.com/kubernetes/ingress-nginx
version: a.b.c.sdfsdf
description: Chart for nginx-ingress
keywords:
- ingress
- nginx
icon: ""

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

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

@ -1,6 +0,0 @@
echo plugin.complete was called
Namespace: default
Num args received: 1
Args received:
:0
Completion ended with directive: ShellCompDirectiveDefault

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

@ -0,0 +1,5 @@
aramis foo-0.1.0-beta.1 -> deployed
athos foo-0.1.0-beta.1 -> deployed
porthos foo-0.1.0-beta.1 -> deployed
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,4 @@
aramis foo-0.1.0-beta.1 -> deployed
athos foo-0.1.0-beta.1 -> deployed
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,5 @@
foo
bar
baz
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

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

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

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

@ -1,2 +0,0 @@
NAME CHART VERSION APP VERSION DESCRIPTION
testing/nginx-ingress a.b.c.sdfsdf Chart for nginx-ingress

@ -1,2 +0,0 @@
NAME CHART VERSION APP VERSION DESCRIPTION
testing/mariadb 0.3.0-0565674 Chart for MariaDB

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

@ -10,7 +10,6 @@ metadata:
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:

@ -10,7 +10,6 @@ metadata:
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:

@ -74,7 +74,6 @@ metadata:
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:

@ -91,7 +91,6 @@ metadata:
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
kube-api-version/test: v1
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:

@ -0,0 +1,114 @@
---
# 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: "16"
kube-version/version: "v1.16.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
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,9 @@
Release "funny-bunny" has been upgraded. Happy Helming!
NAME: funny-bunny
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None
NOTES:
PARENT NOTES

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

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

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

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

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

@ -0,0 +1 @@
password

@ -2,3 +2,8 @@ apiVersion: v1
repositories: repositories:
- name: charts - name: charts
url: "https://charts.helm.sh/stable" url: "https://charts.helm.sh/stable"
- name: firstexample
url: "http://firstexample.com"
- name: secondexample
url: "http://secondexample.com"

@ -0,0 +1,6 @@
dependencies:
- name: subchart-with-notes
repository: file://../chart-with-subchart-notes/charts/subchart-with-notes
version: 0.0.1
digest: sha256:8ca45f73ae3f6170a09b64a967006e98e13cd91eb51e5ab0599bb87296c7df0a
generated: "2021-05-02T15:07:22.1099921+02:00"

@ -4,7 +4,7 @@ kind: Ingress
{{ template "common.metadata" . }} {{ template "common.metadata" . }}
{{- if .Values.ingress.annotations }} {{- if .Values.ingress.annotations }}
annotations: annotations:
{{ include "common.annote" .Values.ingress.annotations | indent 4 }} {{ include "common.annotate" .Values.ingress.annotations | indent 4 }}
{{- end }} {{- end }}
spec: spec:
rules: rules:

@ -11,7 +11,7 @@ Any valid hook may be passed in. Separate multiple hooks with a ",".
"helm.sh/hook": {{printf "%s" . | quote}} "helm.sh/hook": {{printf "%s" . | quote}}
{{- end -}} {{- end -}}
{{- define "common.annote" -}} {{- define "common.annotate" -}}
{{- range $k, $v := . }} {{- range $k, $v := . }}
{{ $k | quote }}: {{ $v | quote }} {{ $k | quote }}: {{ $v | quote }}
{{- end -}} {{- end -}}

@ -4,7 +4,7 @@ kind: Ingress
{{ template "common.metadata" . }} {{ template "common.metadata" . }}
{{- if .Values.ingress.annotations }} {{- if .Values.ingress.annotations }}
annotations: annotations:
{{ include "common.annote" .Values.ingress.annotations | indent 4 }} {{ include "common.annotate" .Values.ingress.annotations | indent 4 }}
{{- end }} {{- end }}
spec: spec:
rules: rules:

@ -11,7 +11,7 @@ Any valid hook may be passed in. Separate multiple hooks with a ",".
"helm.sh/hook": {{printf "%s" . | quote}} "helm.sh/hook": {{printf "%s" . | quote}}
{{- end -}} {{- end -}}
{{- define "common.annote" -}} {{- define "common.annotate" -}}
{{- range $k, $v := . }} {{- range $k, $v := . }}
{{ $k | quote }}: {{ $v | quote }} {{ $k | quote }}: {{ $v | quote }}
{{- end -}} {{- end -}}

@ -48,10 +48,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: uninstallDesc, Long: uninstallDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { return compListReleases(toComplete, args, cfg)
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {

@ -67,6 +67,10 @@ func TestUninstall(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestUninstallCompletion(t *testing.T) {
checkReleaseCompletion(t, "uninstall", true)
}
func TestUninstallFileCompletion(t *testing.T) { func TestUninstallFileCompletion(t *testing.T) {
checkFileCompletion(t, "uninstall", false) checkFileCompletion(t, "uninstall", false)
checkFileCompletion(t, "uninstall myrelease", false) checkFileCompletion(t, "uninstall myrelease", false)

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

Loading…
Cancel
Save