refactor(cmd): move all `helm dep list` methods and tests to the right packages

conflicts resolved

Signed-off-by: Yonatan Kahana <yonatankahana.il@gmail.com>
pull/8077/head
Yonatan Kahana 5 years ago
commit 88086332a6

@ -0,0 +1,15 @@
name: "Close stale issues"
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'
exempt-issue-labels: 'keep+open,v4.x'
days-before-stale: 90
days-before-close: 30

@ -96,22 +96,18 @@ that we're already aware of. It is also worth asking on the Slack channels.
## Milestones
We use milestones to track progress of releases. There are also 2 special milestones used for
helping us keep work organized: `Upcoming - Minor` and `Upcoming - Major`
`Upcoming - Minor` is used for keeping track of issues that aren't assigned to a specific release
but could easily be addressed in a minor release. `Upcoming - Major` keeps track of issues that will
need to be addressed in a major release. For example, if the current version is `3.2.0` an issue/PR
could fall in to one of 4 different active milestones: `3.2.1`, `3.3.0`, `Upcoming - Minor`, or
`Upcoming - Major`. If an issue pertains to a specific upcoming bug or minor release, it would go
into `3.2.1` or `3.3.0`. If the issue/PR does not have a specific milestone yet, but it is likely
that it will land in a `3.X` release, it should go into `Upcoming - Minor`. If the issue/PR is a
large functionality add or change and/or it breaks compatibility, then it should be added to the
`Upcoming - Major` milestone. An issue that we are not sure we will be doing will not be added to
any milestone.
A milestone (and hence release) is considered done when all outstanding issues/PRs have been closed
or moved to another milestone.
We use milestones to track progress of specific planned releases.
For example, if the latest currently-released version is `3.2.1`, an issue/PR which pertains to a
specific upcoming bugfix or feature release could fall into one of two different active milestones:
`3.2.2` or `3.3.0`.
Issues and PRs which are deemed backwards-incompatible may be added to the discussion items for
Helm 4 with [label:v4.x](https://github.com/helm/helm/labels/v4.x). An issue or PR that we are not
sure we will be addressing will not be added to any milestone.
A milestone (and hence release) can be closed when all outstanding issues/PRs have been closed
or moved to another milestone and the associated release has been published.
## Semantic Versioning

@ -5,7 +5,6 @@ TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.g
BINNAME ?= helm
GOPATH = $(shell go env GOPATH)
DEP = $(GOPATH)/bin/dep
GOX = $(GOPATH)/bin/gox
GOIMPORTS = $(GOPATH)/bin/goimports
ARCH = $(shell uname -p)
@ -61,7 +60,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) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------
# test
@ -98,7 +97,7 @@ test-acceptance: TARGETS = linux/amd64
test-acceptance: build build-cross
@if [ -d "${ACCEPTANCE_DIR}" ]; then \
cd ${ACCEPTANCE_DIR} && \
ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH=$(BINDIR) make acceptance; \
ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH='$(BINDIR)' make acceptance; \
else \
echo "You must clone the acceptance_testing repo under $(ACCEPTANCE_DIR)"; \
echo "You can find the acceptance_testing repo at https://github.com/helm/acceptance-testing"; \
@ -179,12 +178,12 @@ checksum:
.PHONY: clean
clean:
@rm -rf $(BINDIR) ./_dist
@rm -rf '$(BINDIR)' ./_dist
.PHONY: release-notes
release-notes:
@if [ ! -d "./_dist" ]; then \
echo "please run 'make fetch-release' first" && \
echo "please run 'make fetch-dist' first" && \
exit 1; \
fi
@if [ -z "${PREVIOUS_RELEASE}" ]; then \

@ -6,7 +6,6 @@ maintainers:
- jdolitsky
- marckhouzam
- mattfarina
- michelleN
- prydonius
- SlickNik
- technosophos
@ -14,6 +13,7 @@ maintainers:
- viglesiasce
emeritus:
- jascott1
- michelleN
- migmartri
- nebril
- seh

@ -43,7 +43,7 @@ If you want to use a package manager:
- [GoFish](https://gofi.sh/) users can use `gofish install helm`.
- [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`
To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide).
To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/).
See the [installation guide](https://helm.sh/docs/intro/install/) for more options,
including installing pre-releases.

@ -21,61 +21,73 @@ import (
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
)
const completionDesc = `
Generate autocompletions script for Helm for the specified shell (bash or zsh).
Generate autocompletions script for Helm for the specified shell.
`
const bashCompDesc = `
Generate the autocompletion script for Helm for the bash shell.
This command can generate shell autocompletions. e.g.
To load completions in your current shell session:
$ source <(helm completion bash)
$ 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
`
Can be sourced as such
const zshCompDesc = `
Generate the autocompletion script for Helm for the zsh shell.
$ source <(helm completion bash)
`
To load completions in your current shell session:
$ source <(helm completion zsh)
var (
completionShells = map[string]func(out io.Writer, cmd *cobra.Command) error{
"bash": runCompletionBash,
"zsh": runCompletionZsh,
}
)
To load completions for every new session, execute once:
$ helm completion zsh > "${fpath[1]}/_helm"
`
func newCompletionCmd(out io.Writer) *cobra.Command {
shells := []string{}
for s := range completionShells {
shells = append(shells, s)
}
cmd := &cobra.Command{
Use: "completion SHELL",
Short: "generate autocompletions script for the specified shell (bash or zsh)",
Use: "completion",
Short: "generate autocompletions script for the specified shell",
Long: completionDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
}
bash := &cobra.Command{
Use: "bash",
Short: "generate autocompletions script for bash",
Long: bashCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletion(out, cmd, args)
return runCompletionBash(out, cmd)
},
ValidArgs: shells,
}
return cmd
zsh := &cobra.Command{
Use: "zsh",
Short: "generate autocompletions script for zsh",
Long: zshCompDesc,
Args: require.NoArgs,
DisableFlagsInUseLine: true,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionZsh(out, cmd)
},
}
func runCompletion(out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("shell not specified")
}
if len(args) > 1 {
return errors.New("too many arguments, expected only the shell type")
}
run, found := completionShells[args[0]]
if !found {
return errors.Errorf("unsupported shell type %q", args[0])
}
cmd.AddCommand(bash, zsh)
return run(out, cmd)
return cmd
}
func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
@ -139,7 +151,7 @@ __helm_compgen() {
fi
for w in "${completions[@]}"; do
if [[ "${w}" = "$1"* ]]; then
# Use printf instead of echo beause it is possible that
# Use printf instead of echo because it is possible that
# the value to print is -n, which would be interpreted
# as a flag to echo
printf "%s\n" "${w}"
@ -244,3 +256,8 @@ __helm_bash_source <(__helm_convert_bash_to_zsh)
out.Write([]byte(zshTail))
return nil
}
// Function to disable file completion
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
}

@ -0,0 +1,58 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"strings"
"testing"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/release"
)
// Check if file completion should be performed according to parameter 'shouldBePerformed'
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{},
Version: 1,
})
testcmd := fmt.Sprintf("__complete %s ''", cmdName)
_, out, err := executeActionCommandC(storage, testcmd)
if err != nil {
t.Errorf("unexpected error, %s", err)
}
if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed {
if shouldBePerformed {
t.Error(fmt.Sprintf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName))
} else {
t.Error(fmt.Sprintf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName))
}
t.Log(out)
}
}
func TestCompletionFileCompletion(t *testing.T) {
checkFileCompletion(t, "completion", false)
checkFileCompletion(t, "completion bash", false)
checkFileCompletion(t, "completion zsh", false)
}

@ -64,6 +64,15 @@ func newCreateCmd(out io.Writer) *cobra.Command {
Short: "create a new chart with the given name",
Long: createDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
// Allow file completion when completing the argument for the name
// which could be a path
return nil, cobra.ShellCompDirectiveDefault
}
// No more completions, so disable file completion
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0]
o.starterDir = helmpath.DataPath("starters")

@ -192,3 +192,8 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
t.Error("Did not find foo.tpl")
}
}
func TestCreateFileCompletion(t *testing.T) {
checkFileCompletion(t, "create", true)
checkFileCompletion(t, "create myname", false)
}

@ -87,6 +87,7 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
Short: "manage a chart's dependencies",
Long: dependencyDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
}
cmd.AddCommand(newDependencyListCmd(out))

@ -16,6 +16,7 @@ limitations under the License.
package main
import (
"fmt"
"io"
"os"
"path/filepath"
@ -65,7 +66,11 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
if client.Verify {
man.Verify = downloader.VerifyIfPossible
}
return man.Build()
err := man.Build()
if e, ok := err.(downloader.ErrRepoNotFound); ok {
return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error())
}
return err
},
}

@ -0,0 +1,22 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestDependencyFileCompletion(t *testing.T) {
checkFileCompletion(t, "dependency", false)
}

@ -52,6 +52,7 @@ func newDocsCmd(out io.Writer) *cobra.Command {
Long: docsDesc,
Hidden: true,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
o.topCmd = cmd.Root()
return o.run(out)

@ -0,0 +1,25 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestDocsFileCompletion(t *testing.T) {
checkFileCompletion(t, "docs", false)
}

@ -35,21 +35,42 @@ func newEnvCmd(out io.Writer) *cobra.Command {
Use: "env",
Short: "helm client environment information",
Long: envHelp,
Args: require.NoArgs,
Args: require.MaximumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
keys := getSortedEnvVarKeys()
return keys, cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
Run: func(cmd *cobra.Command, args []string) {
envVars := settings.EnvVars()
if len(args) == 0 {
// Sort the variables by alphabetical order.
// This allows for a constant output across calls to 'helm env'.
var keys []string
for k := range envVars {
keys = append(keys, k)
}
sort.Strings(keys)
keys := getSortedEnvVarKeys()
for _, k := range keys {
fmt.Fprintf(out, "%s=\"%s\"\n", k, envVars[k])
}
} else {
fmt.Fprintf(out, "%s\n", envVars[args[0]])
}
},
}
return cmd
}
func getSortedEnvVarKeys() []string {
envVars := settings.EnvVars()
var keys []string
for k := range envVars {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}

@ -0,0 +1,35 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestEnv(t *testing.T) {
tests := []cmdTestCase{{
name: "completion for env",
cmd: "__complete env ''",
golden: "output/env-comp.txt",
}}
runTestCmd(t, tests)
}
func TestEnvFileCompletion(t *testing.T) {
checkFileCompletion(t, "env", false)
checkFileCompletion(t, "env HELM_BIN", false)
}

@ -18,16 +18,19 @@ package main
import (
"fmt"
"log"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/repo"
)
const outputFlag = "output"
@ -41,33 +44,37 @@ 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 install. If this is not specified, the latest version is installed")
f.BoolVar(&c.Verify, "verify", false, "verify the package before installing it")
f.StringVar(&c.Version, "version", "", "specify the exact chart version to use. 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")
f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate 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.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
// bindOutputFlag will add the output flag to the given command and bind the
// value to the given format pointer
func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
f := cmd.Flags()
flag := f.VarPF(newOutputValue(output.Table, varRef), outputFlag, "o",
cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o",
fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", ")))
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var formatNames []string
for _, format := range output.Formats() {
if strings.HasPrefix(format, toComplete) {
formatNames = append(formatNames, format)
}
}
return formatNames, completion.BashCompDirectiveDefault
return formatNames, cobra.ShellCompDirectiveDefault
})
if err != nil {
log.Fatal(err)
}
}
type outputValue output.Format
@ -124,3 +131,27 @@ func (p postRenderer) Set(s string) error {
*p.renderer = pr
return nil
}
func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) {
chartInfo := strings.Split(chartRef, "/")
if len(chartInfo) != 2 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
repoName := chartInfo[0]
chartName := chartInfo[1]
path := filepath.Join(settings.RepositoryCache, helmpath.CacheIndexFile(repoName))
var versions []string
if indexFile, err := repo.LoadIndexFile(path); err == nil {
for _, details := range indexFile.Entries[chartName] {
version := details.Metadata.Version
if strings.HasPrefix(version, toComplete) {
versions = append(versions, version)
}
}
}
return versions, cobra.ShellCompDirectiveNoFileComp
}

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

@ -18,11 +18,11 @@ package main
import (
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
@ -41,6 +41,12 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all information for a named release",
Long: getAllHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -57,24 +63,19 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
return compListRevisions(toComplete, cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
f.StringVar(&template, "template", "", "go template for formatting the output, eg: {{.Release.Name}}")
return cmd

@ -45,3 +45,8 @@ func TestGetCmd(t *testing.T) {
func TestGetAllRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get all")
}
func TestGetAllFileCompletion(t *testing.T) {
checkFileCompletion(t, "get all", false)
checkFileCompletion(t, "get all myrelease", false)
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -41,6 +41,12 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all hooks for a named release",
Long: getHooksHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -53,23 +59,17 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0])
}
return compListReleases(toComplete, cfg)
return nil, cobra.ShellCompDirectiveNoFileComp
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
if err != nil {
log.Fatal(err)
}
return nil, completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -40,3 +40,8 @@ func TestGetHooks(t *testing.T) {
func TestGetHooksRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get hooks")
}
func TestGetHooksFileCompletion(t *testing.T) {
checkFileCompletion(t, "get hooks", false)
checkFileCompletion(t, "get hooks myrelease", false)
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -43,6 +43,12 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "download the manifest for a named release",
Long: getManifestHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -53,23 +59,17 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0])
}
return compListReleases(toComplete, cfg)
return nil, cobra.ShellCompDirectiveNoFileComp
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
if err != nil {
log.Fatal(err)
}
return nil, completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -40,3 +40,8 @@ func TestGetManifest(t *testing.T) {
func TestGetManifestRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get manifest")
}
func TestGetManifestFileCompletion(t *testing.T) {
checkFileCompletion(t, "get manifest", false)
checkFileCompletion(t, "get manifest myrelease", false)
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -39,6 +39,12 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the notes for a named release",
Long: getNotesHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -51,23 +57,18 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
return compListRevisions(toComplete, cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -40,3 +40,8 @@ func TestGetNotesCmd(t *testing.T) {
func TestGetNotesRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get notes")
}
func TestGetNotesFileCompletion(t *testing.T) {
checkFileCompletion(t, "get notes", false)
checkFileCompletion(t, "get notes myrelease", false)
}

@ -0,0 +1,25 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestGetFileCompletion(t *testing.T) {
checkFileCompletion(t, "get", false)
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
@ -46,6 +46,12 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the values file for a named release",
Long: getValuesHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
vals, err := client.Run(args[0])
if err != nil {
@ -55,23 +61,19 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
return compListRevisions(toComplete, cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
f.BoolVarP(&client.AllValues, "all", "a", false, "dump all (computed) values")
bindOutputFlag(cmd, &outfmt)

@ -60,3 +60,8 @@ func TestGetValuesRevisionCompletion(t *testing.T) {
func TestGetValuesOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "get values")
}
func TestGetValuesFileCompletion(t *testing.T) {
checkFileCompletion(t, "get values", false)
checkFileCompletion(t, "get values myrelease", false)
}

@ -56,6 +56,11 @@ func debug(format string, v ...interface{}) {
}
}
func warning(format string, v ...interface{}) {
format = fmt.Sprintf("WARNING: %s\n", format)
fmt.Fprintf(os.Stderr, format, v...)
}
func initKubeLogs() {
pflag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
gofs := flag.NewFlagSet("klog", flag.ExitOnError)
@ -68,7 +73,11 @@ func main() {
initKubeLogs()
actionConfig := new(action.Configuration)
cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil {
debug("%+v", err)
os.Exit(1)
}
// run when each command's execute method is called
cobra.OnInitialize(func() {

@ -115,7 +115,11 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
Log: func(format string, v ...interface{}) {},
}
root := newRootCmd(actionConfig, buf, args)
root, err := newRootCmd(actionConfig, buf, args)
if err != nil {
return nil, "", err
}
root.SetOut(buf)
root.SetErr(buf)
root.SetArgs(args)

@ -20,13 +20,13 @@ import (
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/cli/output"
@ -61,6 +61,12 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "fetch release history",
Aliases: []string{"hist"},
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
history, err := getHistory(client, args[0])
if err != nil {
@ -71,14 +77,6 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
bindOutputFlag(cmd, &outfmt)
@ -187,15 +185,18 @@ func min(x, y int) int {
return y
}
func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, completion.BashCompDirective) {
func compListRevisions(toComplete string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
client := action.NewHistory(cfg)
var revisions []string
if hist, err := client.Run(releaseName); err == nil {
for _, release := range hist {
revisions = append(revisions, strconv.Itoa(release.Version))
version := strconv.Itoa(release.Version)
if strings.HasPrefix(version, toComplete) {
revisions = append(revisions, version)
}
}
return revisions, completion.BashCompDirectiveDefault
return revisions, cobra.ShellCompDirectiveNoFileComp
}
return nil, completion.BashCompDirectiveError
return nil, cobra.ShellCompDirectiveError
}

@ -108,3 +108,8 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) {
}}
runTestCmd(t, tests)
}
func TestHistoryFileCompletion(t *testing.T) {
checkFileCompletion(t, "history", false)
checkFileCompletion(t, "history myrelease", false)
}

@ -17,8 +17,8 @@ limitations under the License.
package main
import (
"fmt"
"io"
"log"
"time"
"github.com/pkg/errors"
@ -26,7 +26,6 @@ import (
"github.com/spf13/pflag"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
@ -114,6 +113,9 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "install a chart",
Long: installDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client)
},
RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
@ -124,19 +126,14 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
return compInstall(args, toComplete, client)
})
addInstallFlags(cmd.Flags(), client, valueOpts)
addInstallFlags(cmd, cmd.Flags(), client, valueOpts)
bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer)
return cmd
}
func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
@ -154,6 +151,21 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
requiredArgs := 2
if client.GenerateName {
requiredArgs = 1
}
if len(args) != requiredArgs {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[requiredArgs-1], toComplete)
})
if err != nil {
log.Fatal(err)
}
}
func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {
@ -188,13 +200,12 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
return nil, err
}
validInstallableChart, err := isChartInstallable(chartRequested)
if !validInstallableChart {
if err := checkIfInstallable(chartRequested); err != nil {
return nil, err
}
if chartRequested.Metadata.Deprecated {
fmt.Fprintln(out, "WARNING: This chart is deprecated")
warning("This chart is deprecated")
}
if req := chartRequested.Metadata.Dependencies; req != nil {
@ -230,19 +241,19 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
return client.Run(chartRequested, vals)
}
// isChartInstallable validates if a chart can be installed
// checkIfInstallable validates if a chart can be installed
//
// Application chart type is only installable
func isChartInstallable(ch *chart.Chart) (bool, error) {
func checkIfInstallable(ch *chart.Chart) error {
switch ch.Metadata.Type {
case "", "application":
return true, nil
return nil
}
return false, errors.Errorf("%s charts are not installable", ch.Metadata.Type)
return errors.Errorf("%s charts are not installable", ch.Metadata.Type)
}
// Provide dynamic auto-completion for the install and template commands
func compInstall(args []string, toComplete string, client *action.Install) ([]string, completion.BashCompDirective) {
func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) {
requiredArgs := 1
if client.GenerateName {
requiredArgs = 0
@ -250,5 +261,5 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
if len(args) == requiredArgs {
return compListCharts(toComplete, true)
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
}

@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"fmt"
"testing"
)
@ -208,3 +209,40 @@ func TestInstall(t *testing.T) {
func TestInstallOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "install")
}
func TestInstallVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for install version flag with release name",
cmd: fmt.Sprintf("%s __complete install releasename testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for install version flag with generate-name",
cmd: fmt.Sprintf("%s __complete install --generate-name testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for install version flag too few args",
cmd: fmt.Sprintf("%s __complete install testing/alpine --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for install version flag too many args",
cmd: fmt.Sprintf("%s __complete install releasename testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for install version flag invalid chart",
cmd: fmt.Sprintf("%s __complete install releasename invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}
func TestInstallFileCompletion(t *testing.T) {
checkFileCompletion(t, "install", false)
checkFileCompletion(t, "install --generate-name", true)
checkFileCompletion(t, "install myname", true)
checkFileCompletion(t, "install myname mychart", false)
}

@ -27,7 +27,7 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
name: "lint good chart with bad subcharts",
cmd: fmt.Sprintf("lint %s", testChart),
golden: "output/lint-chart-with-bad-subcharts.txt",
wantError: false,
wantError: true,
}, {
name: "lint good chart with bad subcharts using --with-subcharts flag",
cmd: fmt.Sprintf("lint --with-subcharts %s", testChart),
@ -36,3 +36,8 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
}}
runTestCmd(t, tests)
}
func TestLintFileCompletion(t *testing.T) {
checkFileCompletion(t, "lint", true)
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given
}

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/release"
@ -69,6 +68,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: listHelp,
Aliases: []string{"ls"},
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
if client.AllNamespaces {
if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil {
@ -127,6 +127,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
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.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)
return cmd
@ -186,8 +187,8 @@ func (r *releaseListWriter) WriteYAML(out io.Writer) error {
}
// Provide dynamic auto-completion for release names
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, completion.BashCompDirective) {
completion.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete))
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug)
client := action.NewList(cfg)
client.All = true
@ -197,7 +198,7 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
client.SetStateMask()
results, err := client.Run()
if err != nil {
return nil, completion.BashCompDirectiveDefault
return nil, cobra.ShellCompDirectiveDefault
}
var choices []string
@ -205,5 +206,5 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
choices = append(choices, res.Name)
}
return choices, completion.BashCompDirectiveNoFileComp
return choices, cobra.ShellCompDirectiveNoFileComp
}

@ -235,3 +235,7 @@ func TestListCmd(t *testing.T) {
func TestListOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "list")
}
func TestListFileCompletion(t *testing.T) {
checkFileCompletion(t, "list", false)
}

@ -32,7 +32,6 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/plugin"
)
@ -97,6 +96,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
// This passes all the flags to the subcommand.
DisableFlagParsing: true,
}
// TODO: Make sure a command with this name does not already exist.
baseCmd.AddCommand(c)
@ -105,7 +105,8 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
// We only do this when necessary (for the "completion" and "__complete" commands) to avoid the
// risk of a rogue plugin affecting Helm's normal behavior.
subCmd, _, err := baseCmd.Find(os.Args[1:])
if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == completion.CompRequestCmd)) ||
if (err == nil &&
((subCmd.HasParent() && subCmd.Parent().Name() == "completion") || subCmd.Name() == cobra.ShellCompRequestCmd)) ||
/* for the tests */ subCmd == baseCmd.Root() {
loadCompletionForPlugin(c, plug)
}
@ -247,9 +248,9 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// Only setup dynamic completion if there are no sub-commands. This avoids
// calling plugin.complete at every completion, which greatly simplifies
// development of plugin.complete for plugin developers.
completion.RegisterValidArgsFunc(baseCmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
baseCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return pluginDynamicComp(plugin, cmd, args, toComplete)
})
}
}
// Create fake flags.
@ -322,12 +323,12 @@ func loadFile(path string) (*pluginCommand, error) {
// pluginDynamicComp call the plugin.complete script of the plugin (if available)
// to obtain the dynamic completion choices. It must pass all the flags and sub-commands
// specified in the command-line to the plugin.complete executable (except helm's global flags)
func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
md := plug.Metadata
u, err := processParent(cmd, args)
if err != nil {
return nil, completion.BashCompDirectiveError
return nil, cobra.ShellCompDirectiveError
}
// We will call the dynamic completion script of the plugin
@ -343,12 +344,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t
}
plugin.SetupPluginEnv(settings, md.Name, plug.Dir)
completion.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv))
cobra.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv), settings.Debug)
buf := new(bytes.Buffer)
if err := callPluginExecutable(md.Name, main, argv, buf); err != nil {
// The dynamic completion file is optional for a plugin, so this error is ok.
completion.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()))
return nil, completion.BashCompDirectiveDefault
cobra.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()), settings.Debug)
return nil, cobra.ShellCompDirectiveDefault
}
var completions []string
@ -361,12 +362,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t
// Check if the last line of output is of the form :<integer>, which
// indicates the BashCompletionDirective.
directive := completion.BashCompDirectiveDefault
directive := cobra.ShellCompDirectiveDefault
if len(completions) > 0 {
lastLine := completions[len(completions)-1]
if len(lastLine) > 1 && lastLine[0] == ':' {
if strInt, err := strconv.Atoi(lastLine[1:]); err == nil {
directive = completion.BashCompDirective(strInt)
directive = cobra.ShellCompDirective(strInt)
completions = completions[:len(completions)-1]
}
}

@ -202,3 +202,8 @@ func setFlags(cmd *cobra.Command, flags map[string]string) {
dest.Set(f, v)
}
}
func TestPackageFileCompletion(t *testing.T) {
checkFileCompletion(t, "package", true)
checkFileCompletion(t, "package mypath", true) // Multiple paths can be given
}

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

@ -43,6 +43,14 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command {
Long: pluginInstallDesc,
Aliases: []string{"add"},
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
// We do file completion, in case the plugin is local
return nil, cobra.ShellCompDirectiveDefault
}
// No more completion once the plugin path has been specified
return nil, cobra.ShellCompDirectiveNoFileComp
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args)
},

@ -31,6 +31,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
Use: "list",
Aliases: []string{"ls"},
Short: "list installed Helm plugins",
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
debug("pluginDirs: %s", settings.PluginsDirectory)
plugins, err := plugin.FindPlugins(settings.PluginsDirectory)

@ -298,3 +298,26 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) {
t.Fatalf("Expected 0 plugins, got %d", len(plugins))
}
}
func TestPluginFileCompletion(t *testing.T) {
checkFileCompletion(t, "plugin", false)
}
func TestPluginInstallFileCompletion(t *testing.T) {
checkFileCompletion(t, "plugin install", true)
checkFileCompletion(t, "plugin install mypath", false)
}
func TestPluginListFileCompletion(t *testing.T) {
checkFileCompletion(t, "plugin list", false)
}
func TestPluginUninstallFileCompletion(t *testing.T) {
checkFileCompletion(t, "plugin uninstall", false)
checkFileCompletion(t, "plugin uninstall myplugin", false)
}
func TestPluginUpdateFileCompletion(t *testing.T) {
checkFileCompletion(t, "plugin update", false)
checkFileCompletion(t, "plugin update myplugin", false)
}

@ -24,7 +24,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/plugin"
)
@ -39,6 +38,12 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
Use: "uninstall <plugin>...",
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
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args)
},
@ -46,15 +51,6 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
return o.run(out)
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -24,7 +24,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/plugin"
"helm.sh/helm/v3/pkg/plugin/installer"
)
@ -40,6 +39,12 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
Use: "update <plugin>...",
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
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args)
},
@ -47,15 +52,6 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
return o.run(out)
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -51,6 +51,12 @@ func newPullCmd(out io.Writer) *cobra.Command {
Aliases: []string{"fetch"},
Long: pullDesc,
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 compListCharts(toComplete, false)
},
RunE: func(cmd *cobra.Command, args []string) error {
client.Settings = settings
if client.Version == "" && client.Devel {
@ -69,14 +75,6 @@ func newPullCmd(out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListCharts(toComplete, false)
})
f := cmd.Flags()
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.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
@ -85,5 +83,16 @@ func newPullCmd(out io.Writer) *cobra.Command {
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[0], toComplete)
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -195,3 +195,34 @@ func TestPullCmd(t *testing.T) {
})
}
}
func TestPullVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for pull version flag",
cmd: fmt.Sprintf("%s __complete pull testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for pull version flag too few args",
cmd: fmt.Sprintf("%s __complete pull --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for pull version flag too many args",
cmd: fmt.Sprintf("%s __complete pull testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for pull version flag invalid chart",
cmd: fmt.Sprintf("%s __complete pull invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}
func TestPullFileCompletion(t *testing.T) {
checkFileCompletion(t, "pull", false)
checkFileCompletion(t, "pull repo/chart", false)
}

@ -24,7 +24,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
@ -46,6 +45,12 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "run tests for a release",
Long: releaseTestHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace()
rel, runErr := client.Run(args[0])
@ -72,14 +77,6 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")

@ -0,0 +1,26 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestReleaseTestingFileCompletion(t *testing.T) {
checkFileCompletion(t, "test", false)
checkFileCompletion(t, "test myrelease", false)
}

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

@ -29,6 +29,7 @@ import (
"github.com/gofrs/flock"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require"
@ -59,6 +60,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
Use: "add [NAME] [URL]",
Short: "add a chart repository",
Args: require.ExactArgs(2),
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0]
o.url = args[1]
@ -114,6 +116,17 @@ func (o *repoAddOptions) run(out io.Writer) error {
return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name)
}
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
}
o.password = string(password)
}
c := repo.Entry{
Name: o.name,
URL: o.url,

@ -40,11 +40,12 @@ func TestRepoAddCmd(t *testing.T) {
}
defer srv.Stop()
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
tmpdir := ensure.TempDir(t)
repoFile := filepath.Join(tmpdir, "repositories.yaml")
tests := []cmdTestCase{{
name: "add a repository",
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s", srv.URL(), repoFile),
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
golden: "output/repo-add.txt",
}}
@ -159,3 +160,9 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
}
}
}
func TestRepoAddFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo add", false)
checkFileCompletion(t, "repo add reponame", false)
checkFileCompletion(t, "repo add reponame https://example.com", false)
}

@ -53,6 +53,14 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
Short: "generate an index file given a directory containing packaged charts",
Long: repoIndexDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
// Allow file completion when completing the argument for the directory
return nil, cobra.ShellCompDirectiveDefault
}
// No more completions, so disable file completion
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
o.dir = args[0]
return o.run(out)

@ -165,3 +165,8 @@ func copyFile(dst, src string) error {
return err
}
func TestRepoIndexFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo index", true)
checkFileCompletion(t, "repo index mydir", false)
}

@ -36,6 +36,7 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Aliases: []string{"ls"},
Short: "list chart repositories",
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
f, err := repo.LoadFile(settings.RepositoryConfig)
if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) {

@ -23,3 +23,7 @@ import (
func TestRepoListOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "repo list")
}
func TestRepoListFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo list", false)
}

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo"
)
@ -45,6 +44,9 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
Aliases: []string{"rm"},
Short: "remove one or more chart repositories",
Args: require.MinimumNArgs(1),
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
@ -52,12 +54,6 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
return o.run(out)
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
return compListRepos(toComplete, args), completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -160,3 +160,8 @@ func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string,
t.Errorf("Error cache chart file was not removed for repository %s", repoName)
}
}
func TestRepoRemoveFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo remove", false)
checkFileCompletion(t, "repo remove repo1", false)
}

@ -0,0 +1,25 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestRepoFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo", false)
}

@ -51,6 +51,7 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
Short: "update information of available charts locally from chart repositories",
Long: updateDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache

@ -100,3 +100,7 @@ func TestUpdateCharts(t *testing.T) {
t.Error("Update was not successful")
}
}
func TestRepoUpdateFileCompletion(t *testing.T) {
checkFileCompletion(t, "repo update", false)
}

@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -47,6 +46,17 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "roll back a release to a previous revision",
Long: rollbackDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return compListReleases(toComplete, cfg)
}
if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0])
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
ver, err := strconv.Atoi(args[1])
@ -65,14 +75,6 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
@ -81,6 +83,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails")
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
return cmd
}

@ -68,3 +68,45 @@ func TestRollbackCmd(t *testing.T) {
}}
runTestCmd(t, tests)
}
func TestRollbackRevisionCompletion(t *testing.T) {
mk := func(name string, vers int, status release.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{
Name: name,
Version: vers,
Status: status,
})
}
releases := []*release.Release{
mk("musketeers", 11, release.StatusDeployed),
mk("musketeers", 10, release.StatusSuperseded),
mk("musketeers", 9, release.StatusSuperseded),
mk("musketeers", 8, release.StatusSuperseded),
mk("carabins", 1, release.StatusSuperseded),
}
tests := []cmdTestCase{{
name: "completion for release parameter",
cmd: "__complete rollback ''",
rels: releases,
golden: "output/rollback-comp.txt",
}, {
name: "completion for revision parameter",
cmd: "__complete rollback musketeers ''",
rels: releases,
golden: "output/revision-comp.txt",
}, {
name: "completion for with too many args",
cmd: "__complete rollback musketeers 11 ''",
rels: releases,
golden: "output/rollback-wrong-args-comp.txt",
}}
runTestCmd(t, tests)
}
func TestRollbackFileCompletion(t *testing.T) {
checkFileCompletion(t, "rollback", false)
checkFileCompletion(t, "rollback myrelease", false)
checkFileCompletion(t, "rollback myrelease 1", false)
}

@ -20,6 +20,7 @@ import (
"context"
"fmt"
"io"
"log"
"strings"
"github.com/spf13/cobra"
@ -27,7 +28,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action"
)
@ -50,6 +50,7 @@ Environment variables:
| $HELM_DATA_HOME | set an alternative location for storing Helm data. |
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
| $HELM_MAX_HISTORY | set the maximum number of helm release history. |
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
@ -68,26 +69,27 @@ By default, the default directories depend on the Operating System. The defaults
| Windows | %TEMP%\helm | %APPDATA%\helm | %APPDATA%\helm |
`
func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command {
func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: "helm",
Short: "The Helm package manager for Kubernetes.",
Long: globalUsage,
SilenceUsage: true,
BashCompletionFunction: completion.GetBashCustomFunction(),
// This breaks completion for 'helm help <TAB>'
// The Cobra release following 1.0 will fix this
//ValidArgsFunction: noCompletions, // Disable file completion
}
flags := cmd.PersistentFlags()
settings.AddFlags(flags)
// Setup shell completion for the namespace flag
flag := flags.Lookup("namespace")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
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
// but short enough that the user is not made to wait very long
to := int64(3)
completion.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to))
cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug)
nsNames := []string{}
if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
@ -96,16 +98,19 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
nsNames = append(nsNames, ns.Name)
}
}
return nsNames, completion.BashCompDirectiveNoFileComp
return nsNames, cobra.ShellCompDirectiveNoFileComp
}
}
return nil, completion.BashCompDirectiveDefault
return nil, cobra.ShellCompDirectiveDefault
})
if err != nil {
log.Fatal(err)
}
// Setup shell completion for the kube-context flag
flag = flags.Lookup("kube-context")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
completion.CompDebugln("About to get the different kube-contexts")
err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln("About to get the different kube-contexts", settings.Debug)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
if len(settings.KubeConfig) > 0 {
@ -120,11 +125,15 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
ctxs = append(ctxs, name)
}
}
return ctxs, completion.BashCompDirectiveNoFileComp
return ctxs, cobra.ShellCompDirectiveNoFileComp
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
// We can safely ignore any errors that flags.Parse encounters since
// those errors will be caught later during the call to cmd.Execution.
// This call is required to gather configuration information prior to
@ -164,19 +173,16 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Hidden documentation generator command: 'helm docs'
newDocsCmd(out),
// Setup the special hidden __complete command to allow for dynamic auto-completion
completion.NewCompleteCmd(settings, out),
)
// Add *experimental* subcommands
registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug),
registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil {
// TODO: don't panic here, refactor newRootCmd to return error
panic(err)
return nil, err
}
actionConfig.RegistryClient = registryClient
cmd.AddCommand(
@ -187,5 +193,5 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Find and add plugins
loadPlugins(cmd, out)
return cmd
return cmd, nil
}

@ -121,3 +121,11 @@ func TestUnknownSubCmd(t *testing.T) {
t.Errorf("Expect unknown command error, got %q", err)
}
}
// Need the release of Cobra following 1.0 to be able to disable
// file completion on the root command. Until then, we cannot
// because it would break 'helm help <TAB>'
//
// func TestRootFileCompletion(t *testing.T) {
// checkFileCompletion(t, "", false)
// }

@ -34,6 +34,7 @@ func newSearchCmd(out io.Writer) *cobra.Command {
Use: "search [keyword]",
Short: "search for a keyword in charts",
Long: searchDesc,
ValidArgsFunction: noCompletions, // Disable file completion
}
cmd.AddCommand(newSearchHubCmd(out))

@ -54,3 +54,7 @@ func TestSearchHubCmd(t *testing.T) {
func TestSearchHubOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "search hub")
}
func TestSearchHubFileCompletion(t *testing.T) {
checkFileCompletion(t, "search hub", true) // File completion may be useful when inputing a keyword
}

@ -32,7 +32,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/search"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo"
@ -290,8 +289,8 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
// Provide dynamic auto-completion for commands that operate on charts (e.g., helm show)
// When true, the includeFiles argument indicates that completion should include local files (e.g., local charts)
func compListCharts(toComplete string, includeFiles bool) ([]string, completion.BashCompDirective) {
completion.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete))
func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete), settings.Debug)
noSpace := false
noFile := false
@ -312,7 +311,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion.
noSpace = true
}
}
completion.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
// Now handle completions for url prefixes
for _, url := range []string{"https://", "http://", "file://"} {
@ -328,7 +327,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion.
noSpace = true
}
}
completion.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions), settings.Debug)
// Finally, provide file completion if we need to.
// We only do this if:
@ -347,25 +346,30 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion.
}
}
}
completion.CompDebugln(fmt.Sprintf("Completions after files: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after files: %v", completions), settings.Debug)
// 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, "./", "/")
}
completion.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug)
directive := completion.BashCompDirectiveDefault
directive := cobra.ShellCompDirectiveDefault
if noFile {
directive = directive | completion.BashCompDirectiveNoFileComp
directive = directive | cobra.ShellCompDirectiveNoFileComp
}
if noSpace {
directive = directive | completion.BashCompDirectiveNoSpace
// The completion.BashCompDirective flags do not work for zsh right now.
directive = directive | cobra.ShellCompDirectiveNoSpace
// The cobra.ShellCompDirective flags do not work for zsh right now.
// We handle it ourselves instead.
completions = compEnforceNoSpace(completions)
}
if !includeFiles {
// If we should not include files in the completions,
// we should disable file completion
directive = directive | cobra.ShellCompDirectiveNoFileComp
}
return completions, directive
}
@ -380,7 +384,7 @@ func compEnforceNoSpace(completions []string) []string {
// We only do this if there is a single choice for completion.
if len(completions) == 1 {
completions = append(completions, completions[0]+".")
completion.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions))
cobra.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions), settings.Debug)
}
return completions
}

@ -87,3 +87,7 @@ func TestSearchRepositoriesCmd(t *testing.T) {
func TestSearchRepoOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "search repo")
}
func TestSearchRepoFileCompletion(t *testing.T) {
checkFileCompletion(t, "search repo", true) // File completion may be useful when inputing a keyword
}

@ -0,0 +1,23 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import "testing"
func TestSearchFileCompletion(t *testing.T) {
checkFileCompletion(t, "search", false)
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -60,12 +60,13 @@ func newShowCmd(out io.Writer) *cobra.Command {
Aliases: []string{"inspect"},
Long: showDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
}
// Function providing dynamic auto-completion
validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListCharts(toComplete, true)
}
@ -75,6 +76,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Short: "show all information of the chart",
Long: showAllDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll
output, err := runShow(args, client)
@ -91,6 +93,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Short: "show the chart's values",
Long: showValuesDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues
output, err := runShow(args, client)
@ -107,6 +110,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Short: "show the chart's definition",
Long: showChartDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart
output, err := runShow(args, client)
@ -123,6 +127,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Short: "show the chart's README",
Long: readmeChartDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme
output, err := runShow(args, client)
@ -136,21 +141,32 @@ func newShowCmd(out io.Writer) *cobra.Command {
cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd}
for _, subCmd := range cmds {
addShowFlags(showCommand, subCmd, client)
// Register the completion function for each subcommand
completion.RegisterValidArgsFunc(subCmd, validArgsFunc)
addShowFlags(subCmd, client)
showCommand.AddCommand(subCmd)
}
return showCommand
}
func addShowFlags(showCmd *cobra.Command, subCmd *cobra.Command, client *action.Show) {
func addShowFlags(subCmd *cobra.Command, client *action.Show) {
f := subCmd.Flags()
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
if subCmd.Name() == "values" {
f.StringVar(&client.JSONPathTemplate, "jsonpath", "", "supply a JSONPath expression to filter the output")
}
addChartPathOptionsFlags(f, &client.ChartPathOptions)
showCmd.AddCommand(subCmd)
err := subCmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[0], toComplete)
})
if err != nil {
log.Fatal(err)
}
}
func runShow(args []string, client *action.Show) (string, error) {

@ -80,3 +80,61 @@ func TestShowPreReleaseChart(t *testing.T) {
})
}
}
func TestShowVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for show version flag",
cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for show version flag too few args",
cmd: fmt.Sprintf("%s __complete show chart --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for show version flag too many args",
cmd: fmt.Sprintf("%s __complete show chart testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for show version flag invalid chart",
cmd: fmt.Sprintf("%s __complete show chart invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for show version flag with all",
cmd: fmt.Sprintf("%s __complete show all testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for show version flag with readme",
cmd: fmt.Sprintf("%s __complete show readme testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for show version flag with values",
cmd: fmt.Sprintf("%s __complete show values testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}}
runTestCmd(t, tests)
}
func TestShowFileCompletion(t *testing.T) {
checkFileCompletion(t, "show", false)
}
func TestShowAllFileCompletion(t *testing.T) {
checkFileCompletion(t, "show all", true)
}
func TestShowChartFileCompletion(t *testing.T) {
checkFileCompletion(t, "show chart", true)
}
func TestShowReadmeFileCompletion(t *testing.T) {
checkFileCompletion(t, "show readme", true)
}
func TestShowValuesFileCompletion(t *testing.T) {
checkFileCompletion(t, "show values", true)
}

@ -19,13 +19,13 @@ package main
import (
"fmt"
"io"
"log"
"strings"
"time"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/output"
@ -53,6 +53,12 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "display the status of the named release",
Long: statusHelp,
Args: require.ExactArgs(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)
},
RunE: func(cmd *cobra.Command, args []string) error {
rel, err := client.Run(args[0])
if err != nil {
@ -66,25 +72,21 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.PersistentFlags()
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
return compListRevisions(toComplete, cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
bindOutputFlag(cmd, &outfmt)
return cmd

@ -171,3 +171,8 @@ func TestStatusRevisionCompletion(t *testing.T) {
func TestStatusOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "status")
}
func TestStatusFileCompletion(t *testing.T) {
checkFileCompletion(t, "status", false)
checkFileCompletion(t, "status myrelease", false)
}

@ -20,6 +20,8 @@ import (
"bytes"
"fmt"
"io"
"os"
"path"
"path/filepath"
"regexp"
"sort"
@ -28,7 +30,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/values"
@ -56,6 +57,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "locally render templates",
Long: templateDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client)
},
RunE: func(_ *cobra.Command, args []string) error {
client.DryRun = true
client.ReleaseName = "RELEASE-NAME"
@ -77,10 +81,23 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if rel != nil {
var manifests bytes.Buffer
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
if !client.DisableHooks {
fileWritten := make(map[string]bool)
for _, m := range rel.Hooks {
if client.OutputDir == "" {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
} else {
newDir := client.OutputDir
if client.UseReleaseName {
newDir = filepath.Join(client.OutputDir, client.ReleaseName)
}
err = writeToFile(newDir, m.Path, m.Manifest, fileWritten[m.Path])
if err != nil {
return err
}
fileWritten[m.Path] = true
}
}
}
@ -100,6 +117,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var manifestsToRender []string
for _, f := range showFiles {
missing := true
// Use linux-style filepath separators to unify user's input path
f = filepath.ToSlash(f)
for _, manifestKey := range manifestsKeys {
manifest := splitManifests[manifestKey]
submatch := manifestNameRegex.FindStringSubmatch(manifest)
@ -110,7 +129,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// manifest.Name is rendered using linux-style filepath separators on Windows as
// well as macOS/linux.
manifestPathSplit := strings.Split(manifestName, "/")
manifestPath := filepath.Join(manifestPathSplit...)
// manifest.Path is connected using linux-style filepath separators on Windows as
// well as macOS/linux
manifestPath := strings.Join(manifestPathSplit, "/")
// if the filepath provided matches a manifest path in the
// chart, render that manifest
@ -136,13 +157,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
return compInstall(args, toComplete, client)
})
f := cmd.Flags()
addInstallFlags(f, client, valueOpts)
addInstallFlags(cmd, f, client, valueOpts)
f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates")
f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install")
@ -154,3 +170,50 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return cmd
}
// The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile)
// are coppied from the actions package. This is part of a change to correct a
// bug introduced by #8156. As part of the todo to refactor renderResources
// 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.
func writeToFile(outputDir string, name string, data string, append bool) error {
outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
err := ensureDirectoryForFile(outfileName)
if err != nil {
return err
}
f, err := createOrOpenFile(outfileName, append)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data))
if err != nil {
return err
}
fmt.Printf("wrote %s\n", outfileName)
return nil
}
func createOrOpenFile(filename string, append bool) (*os.File, error) {
if append {
return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
}
return os.Create(filename)
}
func ensureDirectoryForFile(file string) error {
baseDir := path.Dir(file)
_, err := os.Stat(baseDir)
if err != nil && !os.IsNotExist(err) {
return err
}
return os.MkdirAll(baseDir, 0755)
}

@ -124,3 +124,40 @@ func TestTemplateCmd(t *testing.T) {
}
runTestCmd(t, tests)
}
func TestTemplateVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for template version flag with release name",
cmd: fmt.Sprintf("%s __complete template releasename testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for template version flag with generate-name",
cmd: fmt.Sprintf("%s __complete template --generate-name testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for template version flag too few args",
cmd: fmt.Sprintf("%s __complete template testing/alpine --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for template version flag too many args",
cmd: fmt.Sprintf("%s __complete template releasename testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for template version flag invalid chart",
cmd: fmt.Sprintf("%s __complete template releasename invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}
func TestTemplateFileCompletion(t *testing.T) {
checkFileCompletion(t, "template", false)
checkFileCompletion(t, "template --generate-name", true)
checkFileCompletion(t, "template myname", true)
checkFileCompletion(t, "template myname mychart", false)
}

@ -0,0 +1,3 @@
apiVersion: v1
entries: {}
generated: "2020-06-23T10:01:59.2530763-07:00"

@ -1,4 +1,3 @@
WARNING: This chart is deprecated
NAME: aeneas
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default

@ -0,0 +1,16 @@
HELM_BIN
HELM_CACHE_HOME
HELM_CONFIG_HOME
HELM_DATA_HOME
HELM_DEBUG
HELM_KUBEAPISERVER
HELM_KUBECONTEXT
HELM_KUBETOKEN
HELM_MAX_HISTORY
HELM_NAMESPACE
HELM_PLUGINS
HELM_REGISTRY_CONFIG
HELM_REPOSITORY_CACHE
HELM_REPOSITORY_CONFIG
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -1,6 +1,8 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart
[ERROR] Chart.yaml: name is required
@ -8,9 +10,11 @@
[ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] : unable to load chart
validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
Error: 3 chart(s) linted, 1 chart(s) failed
Error: 3 chart(s) linted, 2 chart(s) failed

@ -1,5 +1,7 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
1 chart(s) linted, 0 chart(s) failed
Error: 1 chart(s) linted, 1 chart(s) failed

@ -2,3 +2,4 @@ table
json
yaml
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -3,3 +3,4 @@ Namespace: default
Num args received: 1
Args received:
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -3,3 +3,4 @@ Namespace: default
Num args received: 2
Args received: --myflag
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -3,3 +3,4 @@ Namespace: mynamespace
Num args received: 2
Args received: --myflag start
:2
Completion ended with directive: ShellCompDirectiveNoSpace

@ -3,3 +3,4 @@ Namespace: mynamespace
Num args received: 1
Args received:
:2
Completion ended with directive: ShellCompDirectiveNoSpace

@ -3,3 +3,4 @@ Namespace: default
Num args received: 1
Args received:
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -3,3 +3,4 @@ Namespace: mynamespace
Num args received: 1
Args received:
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -2,4 +2,5 @@
9
10
11
:0
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

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

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

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

@ -1,4 +1,4 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
empty:
- age: Must be greater than or equal to 0/1
- age: Must be greater than or equal to 0

@ -1,5 +1,5 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
empty:
- (root): employmentInfo is required
- age: Must be greater than or equal to 0/1
- age: Must be greater than or equal to 0

@ -1,3 +1,4 @@
aramis
athos
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

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

@ -1,4 +1,4 @@
Error: values don't meet the specifications of the schema(s) in the following chart(s):
subchart-with-schema:
- age: Must be greater than or equal to 0/1
- age: Must be greater than or equal to 0

@ -0,0 +1 @@
Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

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

Loading…
Cancel
Save