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

@ -26,7 +26,7 @@ fi
VERSION=
if [[ -n "${CIRCLE_TAG:-}" ]]; then
VERSION="${CIRCLE_TAG}"
elif [[ "${CIRCLE_BRANCH:-}" == "master" ]]; then
elif [[ "${CIRCLE_BRANCH:-}" == "main" ]]; then
VERSION="canary"
else
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:
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.-->
**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:
push:
branches: [ master ]
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
branches: [ main ]
schedule:
- cron: '29 6 * * 6'

1
.gitignore vendored

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

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

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

88
KEYS

@ -852,3 +852,91 @@ ANQIfZg7P8oNxVDAX+jIsTDxjh8r+S1wsUQcTNop6JMicDbxrBRB13vYIY0Jg4+Z
hS0eN8yqaR533ire0Ur5Vif6+z4A0ifVTZ2hY96B
=nEJu
-----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
INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 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
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 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
GOBIN = $(shell go env GOBIN)
@ -25,7 +25,7 @@ TESTFLAGS :=
LDFLAGS := -w -s
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
# Required for globs to work correctly
@ -77,7 +77,7 @@ all: build
build: $(BINDIR)/$(BINNAME)
$(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
@ -165,7 +165,7 @@ $(GOIMPORTS):
.PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static"
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
dist:

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

@ -54,7 +54,7 @@ Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/)
## 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

@ -31,14 +31,18 @@ Charts are sorted by ref name, alphabetically.
`
func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{
chartList := action.NewChartList(cfg)
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "list all saved charts",
Long: chartListDesc,
Hidden: !FeatureGateOCI.IsEnabled(),
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.
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:
Linux:
$ helm completion bash > /etc/bash_completion.d/helm
MacOS:
$ helm completion bash > /usr/local/etc/bash_completion.d/helm
- Linux:
helm completion bash > /etc/bash_completion.d/helm
- MacOS:
helm completion bash > /usr/local/etc/bash_completion.d/helm
`
const zshCompDesc = `
Generate the autocompletion script for Helm for the zsh shell.
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:
$ helm completion zsh > "${fpath[1]}/_helm"
helm completion zsh > "${fpath[1]}/_helm"
`
const fishCompDesc = `
Generate the autocompletion script for Helm for the fish shell.
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:
$ 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.
`
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 (
noDescFlagName = "no-descriptions"
noDescFlagText = "disable completion descriptions"
@ -80,24 +98,23 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
}
bash := &cobra.Command{
Use: "bash",
Short: "generate autocompletion script for bash",
Long: bashCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
Use: "bash",
Short: "generate autocompletion script for bash",
Long: bashCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionBash(out, cmd)
},
}
bash.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
zsh := &cobra.Command{
Use: "zsh",
Short: "generate autocompletion script for zsh",
Long: zshCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
Use: "zsh",
Short: "generate autocompletion script for zsh",
Long: zshCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionZsh(out, cmd)
},
@ -105,25 +122,36 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
zsh.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
fish := &cobra.Command{
Use: "fish",
Short: "generate autocompletion script for fish",
Long: fishCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
Use: "fish",
Short: "generate autocompletion script for fish",
Long: fishCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionFish(out, cmd)
},
}
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
}
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
// 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)
}
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
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp

@ -29,9 +29,14 @@ import (
func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
storage := storageFixture()
storage.Create(&release.Release{
Name: "myrelease",
Info: &release.Info{Status: release.StatusDeployed},
Chart: &chart.Chart{},
Name: "myrelease",
Info: &release.Info{Status: release.StatusDeployed},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Myrelease-Chart",
Version: "1.2.3",
},
},
Version: 1,
})
@ -57,3 +62,32 @@ func TestCompletionFileCompletion(t *testing.T) {
checkFileCompletion(t, "completion zsh", 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 {
client := action.NewDependency()
cmd := &cobra.Command{
Use: "list CHART",
Aliases: []string{"ls"},
@ -115,5 +114,9 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
return client.List(chartpath, out)
},
}
f := cmd.Flags()
f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table")
return cmd
}

@ -17,7 +17,6 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"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 ensure.HelmHome(t)()
@ -188,7 +187,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
}
// 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 {
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.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
}

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

@ -21,6 +21,7 @@ import (
"fmt"
"log"
"path/filepath"
"sort"
"strings"
"github.com/spf13/cobra"
@ -46,7 +47,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
}
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.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")
@ -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.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.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
}
// 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) {
var formatNames []string
for _, format := range output.Formats() {
for format, desc := range output.FormatsWithDesc() {
if strings.HasPrefix(format, toComplete) {
formatNames = append(formatNames, format)
formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
}
}
// Sort the results to get a deterministic order for the tests
sort.Strings(formatNames)
return formatNames, cobra.ShellCompDirectiveNoFileComp
})
@ -150,7 +155,21 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
for _, details := range indexFile.Entries[chartName] {
version := details.Metadata.Version
if strings.HasPrefix(version, toComplete) {
versions = append(versions, version)
appVersion := details.Metadata.AppVersion
appVersionDesc := ""
if appVersion != "" {
appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
}
created := details.Created.Format("January 2, 2006")
createdDesc := ""
if created != "" {
createdDesc = fmt.Sprintf("Created: %s ", created)
}
deprecated := ""
if details.Metadata.Deprecated {
deprecated = "(deprecated)"
}
versions = append(versions, fmt.Sprintf("%s\t%s%s%s", version, appVersionDesc, createdDesc, deprecated))
}
}
}

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

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

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

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

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

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

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

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

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

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

@ -32,6 +32,7 @@ import (
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
@ -59,6 +60,12 @@ func warning(format string, v ...interface{}) {
}
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)
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil {

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

@ -109,6 +109,10 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) {
runTestCmd(t, tests)
}
func TestHistoryCompletion(t *testing.T) {
checkReleaseCompletion(t, "history", false)
}
func TestHistoryFileCompletion(t *testing.T) {
checkFileCompletion(t, "history", 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.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.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.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")

@ -18,10 +18,39 @@ package main
import (
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"helm.sh/helm/v3/pkg/repo/repotest"
)
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{
// Install, base case
{
@ -207,6 +236,22 @@ func TestInstall(t *testing.T) {
name: "install chart with only crds",
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)

@ -114,7 +114,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags()
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.SortReverse, "reverse", "r", false, "reverse the sort order")
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.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces")
f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value")
f.IntVar(&client.Offset, "offset", 0, "next release index in the list, used to offset from start value")
f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.")
bindOutputFlag(cmd, &outfmt)
@ -193,8 +193,32 @@ func (r *releaseListWriter) WriteYAML(out io.Writer) error {
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
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)
client := action.NewList(cfg)
@ -203,14 +227,16 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
client.Filter = fmt.Sprintf("^%s", toComplete)
client.SetStateMask()
results, err := client.Run()
releases, err := client.Run()
if err != nil {
return nil, cobra.ShellCompDirectiveDefault
}
var choices []string
for _, res := range results {
choices = append(choices, res.Name)
filteredReleases := filterReleases(releases, ignoredReleaseNames)
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

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

@ -277,11 +277,6 @@ func TestPluginDynamicCompletion(t *testing.T) {
cmd: "__complete echo -n mynamespace ''",
golden: "output/plugin_echo_no_directive.txt",
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 {
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) {
checkFileCompletion(t, "plugin", false)
}

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

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

@ -18,6 +18,8 @@ package main
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"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) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"

@ -67,7 +67,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
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) {
var err error
username := usernameOpt
@ -110,7 +110,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
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) {
fmt.Print(prompt)
if silent {

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

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

@ -29,7 +29,7 @@ import (
"github.com/gofrs/flock"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require"
@ -48,6 +48,8 @@ type repoAddOptions struct {
url string
username string
password string
passwordFromStdinOpt bool
passCredentialsAll bool
forceUpdate bool
allowDeprecatedRepos bool
@ -84,6 +86,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.StringVar(&o.username, "username", "", "chart repository username")
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.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")
@ -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.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.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
return cmd
}
@ -112,7 +116,14 @@ func (o *repoAddOptions) run(out io.Writer) error {
}
// 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)
defer cancel()
locked, err := fileLock.TryLockContext(lockCtx, time.Second)
@ -134,14 +145,24 @@ func (o *repoAddOptions) run(out io.Writer) error {
}
if o.username != "" && o.password == "" {
fd := int(os.Stdin.Fd())
fmt.Fprint(out, "Password: ")
password, err := terminal.ReadPassword(fd)
fmt.Fprintln(out)
if err != nil {
return err
if o.passwordFromStdinOpt {
passwordFromStdin, err := io.ReadAll(os.Stdin)
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{
@ -149,6 +170,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
URL: o.url,
Username: o.username,
Password: o.password,
PassCredentialsAll: o.passCredentialsAll,
CertFile: o.certFile,
KeyFile: o.keyFile,
CAFile: o.caFile,

@ -21,6 +21,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"testing"
@ -142,6 +143,18 @@ func TestRepoAddConcurrentDirNotExist(t *testing.T) {
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) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
@ -192,3 +205,33 @@ func TestRepoAddFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo add reponame", 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
import (
"fmt"
"io"
"strings"
@ -131,7 +132,7 @@ func compListRepos(prefix string, ignoredRepoNames []string) []string {
filteredRepos := filterRepos(f.Repositories, ignoredRepoNames)
for _, repo := range filteredRepos {
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 (
"bytes"
"fmt"
"os"
"path/filepath"
"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) {
checkFileCompletion(t, "repo remove", false)
checkFileCompletion(t, "repo remove repo1", false)

@ -32,6 +32,10 @@ import (
const updateDesc = `
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'.
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")
@ -40,21 +44,25 @@ type repoUpdateOptions struct {
update func([]*repo.ChartRepository, io.Writer)
repoFile string
repoCache string
names []string
}
func newRepoUpdateCmd(out io.Writer) *cobra.Command {
o := &repoUpdateOptions{update: updateCharts}
cmd := &cobra.Command{
Use: "update",
Aliases: []string{"up"},
Short: "update information of available charts locally from chart repositories",
Long: updateDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
Use: "update [REPO1 [REPO2 ...]]",
Aliases: []string{"up"},
Short: "update information of available charts locally from chart repositories",
Long: updateDesc,
Args: require.MinimumNArgs(0),
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 {
o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache
o.names = args
return o.run(out)
},
}
@ -63,19 +71,36 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
func (o *repoUpdateOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.repoFile)
if isNotExist(err) || len(f.Repositories) == 0 {
switch {
case isNotExist(err):
return errNoRepositories
case err != nil:
return errors.Wrapf(err, "failed loading file: %s", o.repoFile)
case len(f.Repositories) == 0:
return errNoRepositories
}
var repos []*repo.ChartRepository
for _, cfg := range f.Repositories {
r, err := repo.NewChartRepository(cfg, getter.All(settings))
if err != nil {
updateAllRepos := len(o.names) == 0
if !updateAllRepos {
// Fail early if the user specified an invalid repo to update
if err := checkRequestedRepos(o.names, f.Repositories); err != nil {
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)
@ -99,3 +124,28 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) {
wg.Wait()
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"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -47,26 +48,79 @@ func TestUpdateCmd(t *testing.T) {
t.Fatal(err)
}
if got := out.String(); !strings.Contains(got, "charts") {
t.Errorf("Expected 'charts' got %q", got)
if got := out.String(); !strings.Contains(got, "charts") ||
!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
// 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)
cachePath := filepath.Join(rootDir, "updcustomcache")
_ = os.Mkdir(cachePath, os.ModePerm)
os.Mkdir(cachePath, os.ModePerm)
defer os.RemoveAll(cachePath)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer ts.Stop()
o := &repoUpdateOptions{
update: updateCharts,
repoFile: "testdata/repositories.yaml",
repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
repoCache: cachePath,
}
if err := o.run(&out); err != nil {
b := ioutil.Discard
if err := o.run(b); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(cachePath, "charts-index.yaml")); err != nil {
if _, err := os.Stat(filepath.Join(cachePath, "test-index.yaml")); err != nil {
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) {
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),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return compListReleases(toComplete, cfg)
return compListReleases(toComplete, args, cfg)
}
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
err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
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
to := int64(3)
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(
loadingRules,
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
ctxs := []string{}
for name := range config.Contexts {
comps := []string{}
for name, context := range config.Contexts {
if strings.HasPrefix(name, toComplete) {
ctxs = append(ctxs, name)
comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
}
}
return ctxs, cobra.ShellCompDirectiveNoFileComp
return comps, cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
})

@ -27,7 +27,7 @@ import (
func checkPerms() {
// 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
// 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
if kc == "" {

@ -88,5 +88,5 @@ func TestSearchHubOutputCompletion(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"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -143,7 +144,7 @@ func (o *searchRepoOptions) setupSearchedVersion() {
}
func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) {
if len(o.version) == 0 {
if o.version == "" {
return res, nil
}
@ -154,26 +155,19 @@ func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Res
data := res[:0]
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 {
if _, found := foundNames[r.Name]; found {
// if not returning all versions and already have found a result,
// you're done!
if !o.versions && foundNames[r.Name] {
continue
}
v, err := semver.NewVersion(r.Chart.Version)
if err != nil {
// If the current version number check appears ErrSegmentStartsZero or ErrInvalidPrerelease error and not devel mode, ignore
if (err == semver.ErrSegmentStartsZero || err == semver.ErrInvalidPrerelease) && !o.devel {
continue
}
appendSearchResults(r)
} else if constraint.Check(v) {
appendSearchResults(r)
continue
}
if constraint.Check(v) {
data = append(data, r)
foundNames[r.Name] = true
}
}
@ -194,6 +188,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) {
ind, err := repo.LoadIndexFile(f)
if err != nil {
warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n)
warning("%s", err)
continue
}
@ -307,7 +302,14 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
// First check completions for repos
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)
if strings.HasPrefix(toComplete, repoWithSlash) {
// Must complete with charts within the specified repo
@ -315,15 +317,15 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
noSpace = false
break
} else if strings.HasPrefix(repo, toComplete) {
// Must complete the repo name
completions = append(completions, repoWithSlash)
// Must complete the repo name with the slash, followed by the description
completions = append(completions, fmt.Sprintf("%s\t%s", repoWithSlash, repoDesc))
noSpace = true
}
}
cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
// 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) {
// The user already put in the full url prefix; we don't have
// 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
// be too many choices for the user to find the real repos)
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 {
if strings.HasPrefix(file.Name(), toComplete) {
// 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,
// we provide a hint that a path can also be used
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)

@ -68,14 +68,6 @@ func TestSearchRepositoriesCmd(t *testing.T) {
name: "search for 'maria', expect valid json output",
cmd: "search repo maria --output json",
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",
cmd: "search repo alpine --output yaml",
@ -97,5 +89,5 @@ func TestSearchRepoOutputCompletion(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 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
return compListReleases(toComplete, args, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
rel, err := client.Run(args[0])

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

@ -52,6 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var skipTests bool
client := action.NewInstall(cfg)
valueOpts := &values.Options{}
var kubeVersion string
var extraAPIs []string
var showFiles []string
@ -64,6 +65,14 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compInstall(args, toComplete, client)
},
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.ReleaseName = "RELEASE-NAME"
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(&skipTests, "skip-tests", false, "skip tests from templated output")
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.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
bindPostRenderFlag(cmd, &client.PostRenderer)
@ -188,7 +198,7 @@ func isTestHook(h *release.Hook) bool {
}
// 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
// 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.

@ -74,6 +74,11 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-with-template-lib-archive-dep"),
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",
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>
if [ "$HELM_NAMESPACE" = "default" ]; then
# Output an invalid directive, which should be ignored
echo ":2222"
echo ":0"
# else
# Don't include the directive, to test it is really optional
fi

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

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

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

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

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

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

@ -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
9
10
11
8 App: 1.0, Chart: foo-0.1.0-beta.1
9 App: 1.0, Chart: foo-0.1.0-beta.1
10 App: 1.0, Chart: foo-0.1.0-beta.1
11 App: 1.0, Chart: foo-0.1.0-beta.1
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

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

@ -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
athos
aramis Aramis-chart-0.0.0 -> uninstalled
athos Athos-chart-1.2.3 -> deployed
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

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

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

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

@ -91,7 +91,6 @@ metadata:
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
kube-api-version/test: v1
spec:
type: ClusterIP
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.2.0
0.1.0
0.3.0-rc.1 App: 3.0.0, Created: November 12, 2020
0.2.0 App: 2.3.4, Created: July 9, 2018
0.1.0 App: 1.2.3, Created: June 27, 2018 (deprecated)
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -1 +1 @@
Version: v3.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:
- name: charts
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" . }}
{{- if .Values.ingress.annotations }}
annotations:
{{ include "common.annote" .Values.ingress.annotations | indent 4 }}
{{ include "common.annotate" .Values.ingress.annotations | indent 4 }}
{{- end }}
spec:
rules:

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

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

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

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

@ -67,6 +67,10 @@ func TestUninstall(t *testing.T) {
runTestCmd(t, tests)
}
func TestUninstallCompletion(t *testing.T) {
checkReleaseCompletion(t, "uninstall", true)
}
func TestUninstallFileCompletion(t *testing.T) {
checkFileCompletion(t, "uninstall", 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