Merge branch 'main' into lint-library

pull/11629/head
Brian Dols 3 years ago
commit 5941f8a9c0

@ -5,7 +5,7 @@ jobs:
build: build:
working_directory: ~/helm.sh/helm working_directory: ~/helm.sh/helm
docker: docker:
- image: circleci/golang:1.16 - image: circleci/golang:1.17
auth: auth:
username: $DOCKER_USER username: $DOCKER_USER
@ -13,7 +13,7 @@ jobs:
environment: environment:
GOCACHE: "/tmp/go/cache" GOCACHE: "/tmp/go/cache"
GOLANGCI_LINT_VERSION: "1.36.0" GOLANGCI_LINT_VERSION: "1.43.0"
steps: steps:
- checkout - checkout
@ -26,6 +26,9 @@ jobs:
- run: - run:
name: test name: test
command: make test-coverage command: make test-coverage
- run:
name: test build
command: make
- deploy: - deploy:
name: deploy name: deploy
command: .circleci/deploy.sh command: .circleci/deploy.sh

@ -46,4 +46,8 @@ make build-cross
make dist checksum VERSION="${VERSION}" make dist checksum VERSION="${VERSION}"
echo "Pushing binaries to Azure" echo "Pushing binaries to Azure"
if [[ "${VERSION}" == "canary" ]]; then
az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING" --overwrite
else
az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING" az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING"
fi

@ -12,7 +12,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: '1.16' go-version: '1.17'
- name: Install golangci-lint - name: Install golangci-lint
run: | run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
@ -21,8 +21,8 @@ jobs:
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64* rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env: env:
GOLANGCI_LINT_VERSION: '1.36.0' GOLANGCI_LINT_VERSION: '1.43.0'
GOLANGCI_LINT_SHA256: '9b8856b3a1c9bfbcf3a06b78e94611763b79abd9751c245246787cd3bf0e78a5' GOLANGCI_LINT_SHA256: 'f3515cebec926257da703ba0a2b169e4a322c11dc31a8b4656b50a43e48877f4'
- name: Test style - name: Test style
run: make test-style run: make test-style
- name: Run unit tests - name: Run unit tests

@ -8,12 +8,12 @@ linters:
- dupl - dupl
- gofmt - gofmt
- goimports - goimports
- golint
- gosimple - gosimple
- govet - govet
- ineffassign - ineffassign
- misspell - misspell
- nakedret - nakedret
- revive
- structcheck - structcheck
- unused - unused
- varcheck - varcheck

@ -77,7 +77,7 @@ all: build
build: $(BINDIR)/$(BINNAME) build: $(BINDIR)/$(BINNAME)
$(BINDIR)/$(BINNAME): $(SRC) $(BINDIR)/$(BINNAME): $(SRC)
GO111MODULE=on go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm GO111MODULE=on CGO_ENABLED=0 go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# install # install

@ -5,16 +5,21 @@ maintainers:
- jdolitsky - jdolitsky
- marckhouzam - marckhouzam
- mattfarina - mattfarina
- prydonius - sabre1041
- scottrigby - scottrigby
- SlickNik - SlickNik
- technosophos - technosophos
triage:
- joejulian
- yxxhero
- zonggen
emeritus: emeritus:
- fibonacci1729 - fibonacci1729
- jascott1 - jascott1
- michelleN - michelleN
- migmartri - migmartri
- nebril - nebril
- prydonius
- rimusz - rimusz
- seh - seh
- thomastaylor312 - thomastaylor312

@ -47,10 +47,10 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
} }
if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed { if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed {
if shouldBePerformed { if shouldBePerformed {
t.Error(fmt.Sprintf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)) t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)
} else { } else {
t.Error(fmt.Sprintf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)) t.Errorf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)
} }
t.Log(out) t.Log(out)
} }

@ -50,11 +50,6 @@ func TestDependencyBuildCmd(t *testing.T) {
} }
ociSrv.Run(t, repotest.WithDependingChart(c)) ociSrv.Run(t, repotest.WithDependingChart(c))
err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
if err != nil {
t.Fatal("failed to set environment variable enabling OCI support")
}
dir := func(p ...string) string { dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...) return filepath.Join(append([]string{srv.Root()}, p...)...)
} }

@ -51,11 +51,6 @@ func TestDependencyUpdateCmd(t *testing.T) {
} }
ociSrv.Run(t, repotest.WithDependingChart(c)) ociSrv.Run(t, repotest.WithDependingChart(c))
err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
if err != nil {
t.Fatal("failed to set environment variable enabling OCI support")
}
if err := srv.LinkIndices(); err != nil { if err := srv.LinkIndices(); err != nil {
t.Fatal(err) t.Fatal(err)
} }

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

@ -26,9 +26,9 @@ func TestDocsTypeFlagCompletion(t *testing.T) {
cmd: "__complete docs --type ''", cmd: "__complete docs --type ''",
golden: "output/docs-type-comp.txt", golden: "output/docs-type-comp.txt",
}, { }, {
name: "completion for docs --type", name: "completion for docs --type, no filter",
cmd: "__complete docs --type mar", cmd: "__complete docs --type mar",
golden: "output/docs-type-filtered-comp.txt", golden: "output/docs-type-comp.txt",
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -36,8 +36,11 @@ import (
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
const outputFlag = "output" const (
const postRenderFlag = "post-renderer" outputFlag = "output"
postRenderFlag = "post-renderer"
postRenderArgsFlag = "post-renderer-args"
)
func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL (can specify multiple)") f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL (can specify multiple)")
@ -69,10 +72,8 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var formatNames []string var formatNames []string
for format, desc := range output.FormatsWithDesc() { for format, desc := range output.FormatsWithDesc() {
if strings.HasPrefix(format, toComplete) {
formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc)) formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
} }
}
// Sort the results to get a deterministic order for the tests // Sort the results to get a deterministic order for the tests
sort.Strings(formatNames) sort.Strings(formatNames)
@ -112,33 +113,85 @@ func (o *outputValue) Set(s string) error {
} }
func bindPostRenderFlag(cmd *cobra.Command, varRef *postrender.PostRenderer) { func bindPostRenderFlag(cmd *cobra.Command, varRef *postrender.PostRenderer) {
cmd.Flags().Var(&postRenderer{varRef}, postRenderFlag, "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path") p := &postRendererOptions{varRef, "", []string{}}
cmd.Flags().Var(&postRendererString{p}, postRenderFlag, "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path")
cmd.Flags().Var(&postRendererArgsSlice{p}, postRenderArgsFlag, "an argument to the post-renderer (can specify multiple)")
} }
type postRenderer struct { type postRendererOptions struct {
renderer *postrender.PostRenderer renderer *postrender.PostRenderer
binaryPath string
args []string
}
type postRendererString struct {
options *postRendererOptions
} }
func (p postRenderer) String() string { func (p *postRendererString) String() string {
return "exec" return p.options.binaryPath
} }
func (p postRenderer) Type() string { func (p *postRendererString) Type() string {
return "postrenderer" return "postRendererString"
} }
func (p postRenderer) Set(s string) error { func (p *postRendererString) Set(val string) error {
if s == "" { if val == "" {
return nil return nil
} }
pr, err := postrender.NewExec(s) p.options.binaryPath = val
pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...)
if err != nil { if err != nil {
return err return err
} }
*p.renderer = pr *p.options.renderer = pr
return nil return nil
} }
type postRendererArgsSlice struct {
options *postRendererOptions
}
func (p *postRendererArgsSlice) String() string {
return "[" + strings.Join(p.options.args, ",") + "]"
}
func (p *postRendererArgsSlice) Type() string {
return "postRendererArgsSlice"
}
func (p *postRendererArgsSlice) Set(val string) error {
// a post-renderer defined by a user may accept empty arguments
p.options.args = append(p.options.args, val)
if p.options.binaryPath == "" {
return nil
}
// overwrite if already create PostRenderer by `post-renderer` flags
pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...)
if err != nil {
return err
}
*p.options.renderer = pr
return nil
}
func (p *postRendererArgsSlice) Append(val string) error {
p.options.args = append(p.options.args, val)
return nil
}
func (p *postRendererArgsSlice) Replace(val []string) error {
p.options.args = val
return nil
}
func (p *postRendererArgsSlice) GetSlice() []string {
return p.options.args
}
func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) { func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) {
chartInfo := strings.Split(chartRef, "/") chartInfo := strings.Split(chartRef, "/")
if len(chartInfo) != 2 { if len(chartInfo) != 2 {
@ -153,8 +206,6 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
var versions []string var versions []string
if indexFile, err := repo.LoadIndexFile(path); err == nil { if indexFile, err := repo.LoadIndexFile(path); err == nil {
for _, details := range indexFile.Entries[chartName] { for _, details := range indexFile.Entries[chartName] {
version := details.Metadata.Version
if strings.HasPrefix(version, toComplete) {
appVersion := details.Metadata.AppVersion appVersion := details.Metadata.AppVersion
appVersionDesc := "" appVersionDesc := ""
if appVersion != "" { if appVersion != "" {
@ -169,8 +220,7 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
if details.Metadata.Deprecated { if details.Metadata.Deprecated {
deprecated = "(deprecated)" deprecated = "(deprecated)"
} }
versions = append(versions, fmt.Sprintf("%s\t%s%s%s", version, appVersionDesc, createdDesc, deprecated)) versions = append(versions, fmt.Sprintf("%s\t%s%s%s", details.Metadata.Version, appVersionDesc, createdDesc, deprecated))
}
} }
} }

@ -83,6 +83,13 @@ func outputFlagCompletionTest(t *testing.T, cmdName string) {
rels: releasesMockWithStatus(&release.Info{ rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed, Status: release.StatusDeployed,
}), }),
}, {
name: "completion for output flag, no filter",
cmd: fmt.Sprintf("__complete %s --output jso", cmdName),
golden: "output/output-comp.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
}),
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -31,16 +31,12 @@ import (
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
) )
// FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
var settings = cli.New() var settings = cli.New()
func init() { func init() {
@ -95,15 +91,6 @@ func main() {
} }
} }
func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error {
return func(_ *cobra.Command, _ []string) error {
if !FeatureGateOCI.IsEnabled() {
return FeatureGateOCI.Error()
}
return nil
}
}
// This function loads releases into the memory storage if the // This function loads releases into the memory storage if the
// environment variable is properly set. // environment variable is properly set.
func loadReleasesInMemory(actionConfig *action.Configuration) { func loadReleasesInMemory(actionConfig *action.Configuration) {

@ -60,30 +60,11 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) {
} }
t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd) t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd)
_, out, err := executeActionCommandC(storage, tt.cmd) _, out, err := executeActionCommandC(storage, tt.cmd)
if (err != nil) != tt.wantError { if tt.wantError && err == nil {
t.Errorf("expected error, got '%v'", err) t.Errorf("expected error, got success with the following output:\n%s", out)
} }
if tt.golden != "" { if !tt.wantError && err != nil {
test.AssertGoldenString(t, out, tt.golden) t.Errorf("expected no error, got: '%v'", err)
}
})
}
}
}
func runTestActionCmd(t *testing.T, tests []cmdTestCase) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer resetEnv()()
store := storageFixture()
for _, rel := range tt.rels {
store.Create(rel)
}
_, out, err := executeActionCommandC(store, tt.cmd)
if (err != nil) != tt.wantError {
t.Errorf("expected error, got '%v'", err)
} }
if tt.golden != "" { if tt.golden != "" {
test.AssertGoldenString(t, out, tt.golden) test.AssertGoldenString(t, out, tt.golden)
@ -91,6 +72,7 @@ func runTestActionCmd(t *testing.T, tests []cmdTestCase) {
}) })
} }
} }
}
func storageFixture() *storage.Storage { func storageFixture() *storage.Storage {
return storage.Init(driver.NewMemory()) return storage.Init(driver.NewMemory())

@ -20,7 +20,6 @@ import (
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
@ -191,12 +190,9 @@ func compListRevisions(toComplete string, cfg *action.Configuration, releaseName
var revisions []string var revisions []string
if hist, err := client.Run(releaseName); err == nil { if hist, err := client.Run(releaseName); err == nil {
for _, release := range hist { for _, release := range hist {
version := strconv.Itoa(release.Version)
if strings.HasPrefix(version, toComplete) {
appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion) appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version) chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", version, appVersion, chartDesc)) revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(release.Version), appVersion, chartDesc))
}
} }
return revisions, cobra.ShellCompDirectiveNoFileComp return revisions, cobra.ShellCompDirectiveNoFileComp
} }

@ -95,6 +95,11 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) {
cmd: fmt.Sprintf("__complete %s musketeers --revision ''", cmdName), cmd: fmt.Sprintf("__complete %s musketeers --revision ''", cmdName),
rels: releases, rels: releases,
golden: "output/revision-comp.txt", golden: "output/revision-comp.txt",
}, {
name: "completion for revision flag, no filter",
cmd: fmt.Sprintf("__complete %s musketeers --revision 1", cmdName),
rels: releases,
golden: "output/revision-comp.txt",
}, { }, {
name: "completion for revision flag with too few args", name: "completion for revision flag with too few args",
cmd: fmt.Sprintf("__complete %s --revision ''", cmdName), cmd: fmt.Sprintf("__complete %s --revision ''", cmdName),

@ -49,9 +49,9 @@ a path to an unpacked chart directory or a URL.
To override values in a chart, use either the '--values' flag and pass in a file To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line, to force or use the '--set' flag and pass configuration from the command line, to force
a string value use '--set-string'. In case a value is large and therefore a string value use '--set-string'. You can use '--set-file' to set individual
you want not to use neither '--values' nor '--set', use '--set-file' to read the values from a file when the value itself is too long for the command line
single large value from file. or is dynamically generated.
$ helm install -f myvalues.yaml myredis ./redis $ helm install -f myvalues.yaml myredis ./redis
@ -187,10 +187,6 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
} }
client.ReleaseName = name client.ReleaseName = name
if err := checkOCI(chart); err != nil {
return nil, err
}
cp, err := client.ChartPathOptions.LocateChart(chart, settings) cp, err := client.ChartPathOptions.LocateChart(chart, settings)
if err != nil { if err != nil {
return nil, err return nil, err
@ -223,6 +219,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
// As of Helm 2.4.0, this is treated as a stopping condition: // As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/helm/helm/issues/2209 // https://github.com/helm/helm/issues/2209
if err := action.CheckDependencies(chartRequested, req); err != nil { if err := action.CheckDependencies(chartRequested, req); err != nil {
err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
if client.DependencyUpdate { if client.DependencyUpdate {
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
@ -253,8 +250,10 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
ctx := context.Background() ctx := context.Background()
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
// Handle SIGTERM // Set up channel on which to send signal notifications.
cSignal := make(chan os.Signal) // We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
cSignal := make(chan os.Signal, 2)
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-cSignal <-cSignal

@ -123,7 +123,7 @@ func TestInstall(t *testing.T) {
// Install, using the name-template // Install, using the name-template
{ {
name: "install with name-template", name: "install with name-template",
cmd: "install testdata/testcharts/empty --name-template '{{upper \"foobar\"}}'", cmd: "install testdata/testcharts/empty --name-template '{{ \"foobar\"}}'",
golden: "output/install-name-template.txt", golden: "output/install-name-template.txt",
}, },
// Install, perform chart verification along the way. // Install, perform chart verification along the way.
@ -254,7 +254,7 @@ func TestInstall(t *testing.T) {
}, },
} }
runTestActionCmd(t, tests) runTestCmd(t, tests)
} }
func TestInstallOutputCompletion(t *testing.T) { func TestInstallOutputCompletion(t *testing.T) {
@ -275,6 +275,10 @@ func TestInstallVersionCompletion(t *testing.T) {
name: "completion for install version flag with generate-name", name: "completion for install version flag with generate-name",
cmd: fmt.Sprintf("%s __complete install --generate-name testing/alpine --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete install --generate-name testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt", golden: "output/version-comp.txt",
}, {
name: "completion for install version flag, no filter",
cmd: fmt.Sprintf("%s __complete install releasename testing/alpine --version 0.3", repoSetup),
golden: "output/version-comp.txt",
}, { }, {
name: "completion for install version flag too few args", name: "completion for install version flag too few args",
cmd: fmt.Sprintf("%s __complete install testing/alpine --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete install testing/alpine --version ''", repoSetup),

@ -29,6 +29,7 @@ import (
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/lint/support"
) )
var longLintHelp = ` var longLintHelp = `
@ -76,12 +77,23 @@ func newLintCmd(out io.Writer) *cobra.Command {
var message strings.Builder var message strings.Builder
failed := 0 failed := 0
errorsOrWarnings := 0
for _, path := range paths { for _, path := range paths {
fmt.Fprintf(&message, "==> Linting %s\n", path)
result := client.Run([]string{path}, vals) result := client.Run([]string{path}, vals)
// If there is no errors/warnings and quiet flag is set
// go to the next chart
hasWarningsOrErrors := action.HasWarningsOrErrors(result)
if hasWarningsOrErrors {
errorsOrWarnings++
}
if client.Quiet && !hasWarningsOrErrors {
continue
}
fmt.Fprintf(&message, "==> Linting %s\n", path)
// All the Errors that are generated by a chart // All the Errors that are generated by a chart
// that failed a lint will be included in the // that failed a lint will be included in the
// results.Messages so we only need to print // results.Messages so we only need to print
@ -93,8 +105,10 @@ func newLintCmd(out io.Writer) *cobra.Command {
} }
for _, msg := range result.Messages { for _, msg := range result.Messages {
if !client.Quiet || msg.Severity > support.InfoSev {
fmt.Fprintf(&message, "%s\n", msg) fmt.Fprintf(&message, "%s\n", msg)
} }
}
if len(result.Errors) != 0 { if len(result.Errors) != 0 {
failed++ failed++
@ -112,7 +126,9 @@ func newLintCmd(out io.Writer) *cobra.Command {
if failed > 0 { if failed > 0 {
return errors.New(summary) return errors.New(summary)
} }
if !client.Quiet || errorsOrWarnings > 0 {
fmt.Fprintln(out, summary) fmt.Fprintln(out, summary)
}
return nil return nil
}, },
} }
@ -120,6 +136,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
return cmd return cmd

@ -37,6 +37,27 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestLintCmdWithQuietFlag(t *testing.T) {
testChart1 := "testdata/testcharts/alpine"
testChart2 := "testdata/testcharts/chart-bad-requirements"
tests := []cmdTestCase{{
name: "lint good chart using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s", testChart1),
golden: "output/lint-quiet.txt",
}, {
name: "lint two charts, one with error using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s %s", testChart1, testChart2),
golden: "output/lint-quiet-with-error.txt",
wantError: true,
}, {
name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt",
}}
runTestCmd(t, tests)
}
func TestLintFileCompletion(t *testing.T) { func TestLintFileCompletion(t *testing.T) {
checkFileCompletion(t, "lint", true) checkFileCompletion(t, "lint", true)
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given

@ -157,8 +157,8 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string) *relea
Namespace: r.Namespace, Namespace: r.Namespace,
Revision: strconv.Itoa(r.Version), Revision: strconv.Itoa(r.Version),
Status: r.Info.Status.String(), Status: r.Info.Status.String(),
Chart: fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version), Chart: formatChartname(r.Chart),
AppVersion: r.Chart.Metadata.AppVersion, AppVersion: formatAppVersion(r.Chart),
} }
t := "-" t := "-"
@ -224,7 +224,14 @@ func compListReleases(toComplete string, ignoredReleaseNames []string, cfg *acti
client := action.NewList(cfg) client := action.NewList(cfg)
client.All = true client.All = true
client.Limit = 0 client.Limit = 0
client.Filter = fmt.Sprintf("^%s", toComplete) // Do not filter so as to get the entire list of releases.
// This will allow zsh and fish to match completion choices
// on other criteria then prefix. For example:
// helm status ingress<TAB>
// can match
// helm status nginx-ingress
//
// client.Filter = fmt.Sprintf("^%s", toComplete)
client.SetStateMask() client.SetStateMask()
releases, err := client.Run() releases, err := client.Run()

@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) { func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{} known := []string{}
unknown := []string{} unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config"} kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--insecure-skip-tls-verify", "--tls-server-name"}
knownArg := func(a string) bool { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {
@ -313,7 +313,7 @@ func loadFile(path string) (*pluginCommand, error) {
cmds := new(pluginCommand) cmds := new(pluginCommand)
b, err := ioutil.ReadFile(path) b, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return cmds, errors.New(fmt.Sprintf("File (%s) not provided by plugin. No plugin auto-completion possible.", path)) return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path)
} }
err = yaml.Unmarshal(b, cmds) err = yaml.Unmarshal(b, cmds)

@ -48,7 +48,7 @@ If '--keyring' is not specified, Helm usually defaults to the public keyring
unless your environment is otherwise configured. unless your environment is otherwise configured.
` `
func newPackageCmd(out io.Writer) *cobra.Command { func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPackage() client := action.NewPackage()
valueOpts := &values.Options{} valueOpts := &values.Options{}
@ -92,6 +92,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: p, Getters: p,
Debug: settings.Debug, Debug: settings.Debug,
RegistryClient: cfg.RegistryClient,
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
} }

@ -16,14 +16,13 @@ limitations under the License.
package main package main
import ( import (
"bytes" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
"github.com/spf13/cobra"
"helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
@ -118,15 +117,12 @@ func TestPackage(t *testing.T) {
if err := os.MkdirAll("toot", 0777); err != nil { if err := os.MkdirAll("toot", 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
var buf bytes.Buffer
c := newPackageCmd(&buf)
// This is an unfortunate byproduct of the tmpdir // This is an unfortunate byproduct of the tmpdir
if v, ok := tt.flags["keyring"]; ok && len(v) > 0 { if v, ok := tt.flags["keyring"]; ok && len(v) > 0 {
tt.flags["keyring"] = filepath.Join(origDir, v) tt.flags["keyring"] = filepath.Join(origDir, v)
} }
setFlags(c, tt.flags)
re := regexp.MustCompile(tt.expect) re := regexp.MustCompile(tt.expect)
adjustedArgs := make([]string, len(tt.args)) adjustedArgs := make([]string, len(tt.args))
@ -134,7 +130,16 @@ func TestPackage(t *testing.T) {
adjustedArgs[i] = filepath.Join(origDir, f) adjustedArgs[i] = filepath.Join(origDir, f)
} }
err := c.RunE(c, adjustedArgs) cmd := []string{"package"}
if len(adjustedArgs) > 0 {
cmd = append(cmd, adjustedArgs...)
}
for k, v := range tt.flags {
if v != "0" {
cmd = append(cmd, fmt.Sprintf("--%s=%s", k, v))
}
}
_, _, err = executeActionCommand(strings.Join(cmd, " "))
if err != nil { if err != nil {
if tt.err && re.MatchString(err.Error()) { if tt.err && re.MatchString(err.Error()) {
return return
@ -142,10 +147,6 @@ func TestPackage(t *testing.T) {
t.Fatalf("%q: expected error %q, got %q", tt.name, tt.expect, err) t.Fatalf("%q: expected error %q, got %q", tt.name, tt.expect, err)
} }
if !re.Match(buf.Bytes()) {
t.Errorf("%q: expected output %q, got %q", tt.name, tt.expect, buf.String())
}
if len(tt.hasfile) > 0 { if len(tt.hasfile) > 0 {
if fi, err := os.Stat(tt.hasfile); err != nil { if fi, err := os.Stat(tt.hasfile); err != nil {
t.Errorf("%q: expected file %q, got err %q", tt.name, tt.hasfile, err) t.Errorf("%q: expected file %q, got err %q", tt.name, tt.hasfile, err)
@ -168,26 +169,21 @@ func TestPackage(t *testing.T) {
func TestSetAppVersion(t *testing.T) { func TestSetAppVersion(t *testing.T) {
var ch *chart.Chart var ch *chart.Chart
expectedAppVersion := "app-version-foo" expectedAppVersion := "app-version-foo"
chartToPackage := "testdata/testcharts/alpine"
dir := ensure.TempDir(t) dir := ensure.TempDir(t)
cmd := fmt.Sprintf("package %s --destination=%s --app-version=%s", chartToPackage, dir, expectedAppVersion)
c := newPackageCmd(&bytes.Buffer{}) _, output, err := executeActionCommand(cmd)
flags := map[string]string{ if err != nil {
"destination": dir, t.Logf("Output: %s", output)
"app-version": expectedAppVersion, t.Fatal(err)
}
setFlags(c, flags)
if err := c.RunE(c, []string{"testdata/testcharts/alpine"}); err != nil {
t.Errorf("unexpected error %q", err)
} }
chartPath := filepath.Join(dir, "alpine-0.1.0.tgz") chartPath := filepath.Join(dir, "alpine-0.1.0.tgz")
if fi, err := os.Stat(chartPath); err != nil { if fi, err := os.Stat(chartPath); err != nil {
t.Errorf("expected file %q, got err %q", chartPath, err) t.Errorf("expected file %q, got err %q", chartPath, err)
} else if fi.Size() == 0 { } else if fi.Size() == 0 {
t.Errorf("file %q has zero bytes.", chartPath) t.Errorf("file %q has zero bytes.", chartPath)
} }
ch, err := loader.Load(chartPath) ch, err = loader.Load(chartPath)
if err != nil { if err != nil {
t.Fatalf("unexpected error loading packaged chart: %v", err) t.Fatalf("unexpected error loading packaged chart: %v", err)
} }
@ -196,13 +192,6 @@ func TestSetAppVersion(t *testing.T) {
} }
} }
func setFlags(cmd *cobra.Command, flags map[string]string) {
dest := cmd.Flags()
for f, v := range flags {
dest.Set(f, v)
}
}
func TestPackageFileCompletion(t *testing.T) { func TestPackageFileCompletion(t *testing.T) {
checkFileCompletion(t, "package", true) checkFileCompletion(t, "package", true)
checkFileCompletion(t, "package mypath", true) // Multiple paths can be given checkFileCompletion(t, "package mypath", true) // Multiple paths can be given

@ -18,7 +18,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"strings"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -82,10 +81,8 @@ func compListPlugins(toComplete string, ignoredPluginNames []string) []string {
if err == nil && len(plugins) > 0 { if err == nil && len(plugins) > 0 {
filteredPlugins := filterPlugins(plugins, ignoredPluginNames) filteredPlugins := filterPlugins(plugins, ignoredPluginNames)
for _, p := range filteredPlugins { for _, p := range filteredPlugins {
if strings.HasPrefix(p.Metadata.Name, toComplete) {
pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata.Name, p.Metadata.Usage)) pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata.Name, p.Metadata.Usage))
} }
} }
}
return pNames return pNames
} }

@ -307,6 +307,11 @@ func TestPluginCmdsCompletion(t *testing.T) {
cmd: "__complete plugin update ''", cmd: "__complete plugin update ''",
golden: "output/plugin_list_comp.txt", golden: "output/plugin_list_comp.txt",
rels: []*release.Release{}, rels: []*release.Release{},
}, {
name: "completion for plugin update, no filter",
cmd: "__complete plugin update full",
golden: "output/plugin_list_comp.txt",
rels: []*release.Release{},
}, { }, {
name: "completion for plugin update repetition", name: "completion for plugin update repetition",
cmd: "__complete plugin update args ''", cmd: "__complete plugin update args ''",
@ -317,6 +322,11 @@ func TestPluginCmdsCompletion(t *testing.T) {
cmd: "__complete plugin uninstall ''", cmd: "__complete plugin uninstall ''",
golden: "output/plugin_list_comp.txt", golden: "output/plugin_list_comp.txt",
rels: []*release.Release{}, rels: []*release.Release{},
}, {
name: "completion for plugin uninstall, no filter",
cmd: "__complete plugin uninstall full",
golden: "output/plugin_list_comp.txt",
rels: []*release.Release{},
}, { }, {
name: "completion for plugin uninstall repetition", name: "completion for plugin uninstall repetition",
cmd: "__complete plugin uninstall args ''", cmd: "__complete plugin uninstall args ''",

@ -64,10 +64,6 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
if err := checkOCI(args[0]); err != nil {
return err
}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
output, err := client.Run(args[i]) output, err := client.Run(args[i])
if err != nil { if err != nil {
@ -84,7 +80,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it") f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification") f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded") f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this") f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

@ -34,7 +34,6 @@ func TestPullCmd(t *testing.T) {
} }
defer srv.Stop() defer srv.Stop()
os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
ociSrv, err := repotest.NewOCIServer(t, srv.Root()) ociSrv, err := repotest.NewOCIServer(t, srv.Root())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -371,6 +370,10 @@ func TestPullVersionCompletion(t *testing.T) {
name: "completion for pull version flag", name: "completion for pull version flag",
cmd: fmt.Sprintf("%s __complete pull testing/alpine --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete pull testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt", golden: "output/version-comp.txt",
}, {
name: "completion for pull version flag, no filter",
cmd: fmt.Sprintf("%s __complete pull testing/alpine --version 0.3", repoSetup),
golden: "output/version-comp.txt",
}, { }, {
name: "completion for pull version flag too few args", name: "completion for pull version flag too few args",
cmd: fmt.Sprintf("%s __complete pull --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete pull --version ''", repoSetup),

@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
experimental "helm.sh/helm/v3/internal/experimental/action"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/pusher"
) )
const pushDesc = ` const pushDesc = `
@ -35,15 +35,30 @@ it will also be uploaded.
` `
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := experimental.NewPushWithOpts(experimental.WithPushConfig(cfg)) client := action.NewPushWithOpts(action.WithPushConfig(cfg))
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [chart] [remote]", Use: "push [chart] [remote]",
Short: "push a chart to remote", Short: "push a chart to remote",
Long: pushDesc, Long: pushDesc,
Hidden: !FeatureGateOCI.IsEnabled(),
PersistentPreRunE: checkOCIFeatureGate(),
Args: require.MinimumNArgs(2), Args: require.MinimumNArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
// Do file completion for the chart file to push
return nil, cobra.ShellCompDirectiveDefault
}
if len(args) == 1 {
providers := []pusher.Provider(pusher.All(settings))
var comps []string
for _, p := range providers {
for _, scheme := range p.Schemes {
comps = append(comps, fmt.Sprintf("%s://", scheme))
}
}
return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
chartRef := args[0] chartRef := args[0]
remote := args[1] remote := args[1]

@ -0,0 +1,27 @@
/*
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 TestPushFileCompletion(t *testing.T) {
checkFileCompletion(t, "push", true)
checkFileCompletion(t, "push package.tgz", false)
checkFileCompletion(t, "push package.tgz oci://localhost:5000", false)
}

@ -32,8 +32,6 @@ func newRegistryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Use: "registry", Use: "registry",
Short: "login to or logout from a registry", Short: "login to or logout from a registry",
Long: registryHelp, Long: registryHelp,
Hidden: !FeatureGateOCI.IsEnabled(),
PersistentPreRunE: checkOCIFeatureGate(),
} }
cmd.AddCommand( cmd.AddCommand(
newRegistryLoginCmd(cfg, out), newRegistryLoginCmd(cfg, out),

@ -25,11 +25,10 @@ import (
"os" "os"
"strings" "strings"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term" //nolint
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
experimental "helm.sh/helm/v3/internal/experimental/action"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
) )
@ -46,7 +45,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
Short: "login to a registry", Short: "login to a registry",
Long: registryLoginDesc, Long: registryLoginDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
Hidden: !FeatureGateOCI.IsEnabled(), ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
hostname := args[0] hostname := args[0]
@ -55,7 +54,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
return err return err
} }
return experimental.NewRegistryLogin(cfg).Run(out, hostname, username, password, insecureOpt) return action.NewRegistryLogin(cfg).Run(out, hostname, username, password, insecureOpt)
}, },
} }

@ -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 TestRegistryLoginFileCompletion(t *testing.T) {
checkFileCompletion(t, "registry login", false)
}

@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
experimental "helm.sh/helm/v3/internal/experimental/action"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
) )
@ -36,10 +35,10 @@ func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Comma
Short: "logout from a registry", Short: "logout from a registry",
Long: registryLogoutDesc, Long: registryLogoutDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
Hidden: !FeatureGateOCI.IsEnabled(), ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
hostname := args[0] hostname := args[0]
return experimental.NewRegistryLogout(cfg).Run(out, hostname) return action.NewRegistryLogout(cfg).Run(out, hostname)
}, },
} }
} }

@ -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 TestRegistryLogoutFileCompletion(t *testing.T) {
checkFileCompletion(t, "registry logout", false)
}

@ -119,7 +119,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
repoFileExt := filepath.Ext(o.repoFile) repoFileExt := filepath.Ext(o.repoFile)
var lockPath string var lockPath string
if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) { if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) {
lockPath = strings.Replace(o.repoFile, repoFileExt, ".lock", 1) lockPath = strings.TrimSuffix(o.repoFile, repoFileExt) + ".lock"
} else { } else {
lockPath = o.repoFile + ".lock" lockPath = o.repoFile + ".lock"
} }
@ -177,6 +177,11 @@ func (o *repoAddOptions) run(out io.Writer) error {
InsecureSkipTLSverify: o.insecureSkipTLSverify, InsecureSkipTLSverify: o.insecureSkipTLSverify,
} }
// Check if the repo name is legal
if strings.Contains(o.name, "/") {
return errors.Errorf("repository name (%s) contains '/', please specify a different name without '/'", o.name)
}
// If the repo exists do one of two things: // If the repo exists do one of two things:
// 1. If the configuration for the name is the same continue without error // 1. If the configuration for the name is the same continue without error
// 2. When the config is different require --force-update // 2. When the config is different require --force-update

@ -48,7 +48,11 @@ func TestRepoAddCmd(t *testing.T) {
} }
defer srv2.Stop() defer srv2.Stop()
tmpdir := ensure.TempDir(t) tmpdir := filepath.Join(ensure.TempDir(t), "path-component.yaml/data")
err = os.MkdirAll(tmpdir, 0777)
if err != nil {
t.Fatal(err)
}
repoFile := filepath.Join(tmpdir, "repositories.yaml") repoFile := filepath.Join(tmpdir, "repositories.yaml")
tests := []cmdTestCase{ tests := []cmdTestCase{
@ -131,6 +135,39 @@ func TestRepoAdd(t *testing.T) {
} }
} }
func TestRepoAddCheckLegalName(t *testing.T) {
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer ts.Stop()
defer resetEnv()()
const testRepoName = "test-hub/test-name"
rootDir := ensure.TempDir(t)
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
o := &repoAddOptions{
name: testRepoName,
url: ts.URL(),
forceUpdate: false,
deprecatedNoUpdate: true,
repoFile: repoFile,
}
os.Setenv(xdg.CacheHomeEnvVar, rootDir)
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
if err := o.run(ioutil.Discard); err != nil {
if wantErrorMsg != err.Error() {
t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg)
}
} else {
t.Fatalf("expect reported an error.")
}
}
func TestRepoAddConcurrentGoRoutines(t *testing.T) { func TestRepoAddConcurrentGoRoutines(t *testing.T) {
const testName = "test-name" const testName = "test-name"
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml") repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")

@ -19,7 +19,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"strings"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -39,8 +38,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
f, err := repo.LoadFile(settings.RepositoryConfig) f, _ := repo.LoadFile(settings.RepositoryConfig)
if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) { if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) {
return errors.New("no repositories to show") return errors.New("no repositories to show")
} }
@ -131,10 +130,8 @@ func compListRepos(prefix string, ignoredRepoNames []string) []string {
if err == nil && len(f.Repositories) > 0 { if err == nil && len(f.Repositories) > 0 {
filteredRepos := filterRepos(f.Repositories, ignoredRepoNames) filteredRepos := filterRepos(f.Repositories, ignoredRepoNames)
for _, repo := range filteredRepos { for _, repo := range filteredRepos {
if strings.HasPrefix(repo.Name, prefix) {
rNames = append(rNames, fmt.Sprintf("%s\t%s", repo.Name, repo.URL)) rNames = append(rNames, fmt.Sprintf("%s\t%s", repo.Name, repo.URL))
} }
} }
}
return rNames return rNames
} }

@ -197,6 +197,10 @@ func TestRepoRemoveCompletion(t *testing.T) {
name: "completion for repo remove", name: "completion for repo remove",
cmd: fmt.Sprintf("%s __completeNoDesc repo remove ''", repoSetup), cmd: fmt.Sprintf("%s __completeNoDesc repo remove ''", repoSetup),
golden: "output/repo_list_comp.txt", golden: "output/repo_list_comp.txt",
}, {
name: "completion for repo remove, no filter",
cmd: fmt.Sprintf("%s __completeNoDesc repo remove fo", repoSetup),
golden: "output/repo_list_comp.txt",
}, { }, {
name: "completion for repo remove repetition", name: "completion for repo remove repetition",
cmd: fmt.Sprintf("%s __completeNoDesc repo remove foo ''", repoSetup), cmd: fmt.Sprintf("%s __completeNoDesc repo remove foo ''", repoSetup),

@ -133,8 +133,8 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdate
wg.Wait() wg.Wait()
if len(repoFailList) > 0 && failOnRepoUpdateFail { if len(repoFailList) > 0 && failOnRepoUpdateFail {
return errors.New(fmt.Sprintf("Failed to update the following repositories: %s", return fmt.Errorf("Failed to update the following repositories: %s",
repoFailList)) repoFailList)
} }
fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈") fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈")

@ -29,8 +29,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
@ -46,7 +46,7 @@ Common actions for Helm:
Environment variables: Environment variables:
| Name | Description | | Name | Description |
|------------------------------------|-----------------------------------------------------------------------------------| |------------------------------------|---------------------------------------------------------------------------------------------------|
| $HELM_CACHE_HOME | set an alternative location for storing cached files. | | $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. | | $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $HELM_DATA_HOME | set an alternative location for storing Helm data. | | $HELM_DATA_HOME | set an alternative location for storing Helm data. |
@ -67,6 +67,9 @@ Environment variables:
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. | | $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. | | $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | | $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)|
Helm stores cache, configuration, and data based on the following configuration order: Helm stores cache, configuration, and data based on the following configuration order:
@ -106,10 +109,8 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
nsNames := []string{} nsNames := []string{}
if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil { if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
for _, ns := range namespaces.Items { for _, ns := range namespaces.Items {
if strings.HasPrefix(ns.Name, toComplete) {
nsNames = append(nsNames, ns.Name) nsNames = append(nsNames, ns.Name)
} }
}
return nsNames, cobra.ShellCompDirectiveNoFileComp return nsNames, cobra.ShellCompDirectiveNoFileComp
} }
} }
@ -133,10 +134,8 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil { &clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
comps := []string{} comps := []string{}
for name, context := range config.Contexts { for name, context := range config.Contexts {
if strings.HasPrefix(name, toComplete) {
comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster)) comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
} }
}
return comps, cobra.ShellCompDirectiveNoFileComp return comps, cobra.ShellCompDirectiveNoFileComp
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
@ -155,6 +154,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
registryClient, err := registry.NewClient( registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug), registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(out), registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptCredentialsFile(settings.RegistryConfig),
) )
@ -169,9 +169,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
newCreateCmd(out), newCreateCmd(out),
newDependencyCmd(actionConfig, out), newDependencyCmd(actionConfig, out),
newPullCmd(actionConfig, out), newPullCmd(actionConfig, out),
newShowCmd(out), newShowCmd(actionConfig, out),
newLintCmd(out), newLintCmd(out),
newPackageCmd(out), newPackageCmd(actionConfig, out),
newRepoCmd(out), newRepoCmd(out),
newSearchCmd(out), newSearchCmd(out),
newVerifyCmd(out), newVerifyCmd(out),
@ -197,7 +197,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
newDocsCmd(out), newDocsCmd(out),
) )
// Add *experimental* subcommands
cmd.AddCommand( cmd.AddCommand(
newRegistryCmd(actionConfig, out), newRegistryCmd(actionConfig, out),
newPushCmd(actionConfig, out), newPushCmd(actionConfig, out),
@ -262,12 +261,3 @@ func checkForExpiredRepos(repofile string) {
} }
} }
// When dealing with OCI-based charts, ensure that the user has
// enabled the experimental feature gate prior to continuing
func checkOCI(ref string) error {
if registry.IsOCI(ref) && !FeatureGateOCI.IsEnabled() {
return FeatureGateOCI.Error()
}
return nil
}

@ -1,4 +1,4 @@
// +build !windows //go:build !windows
/* /*
Copyright The Helm Authors. Copyright The Helm Authors.

@ -1,4 +1,4 @@
// +build !windows //go:build !windows
/* /*
Copyright The Helm Authors. Copyright The Helm Authors.
@ -21,7 +21,6 @@ package main
import ( import (
"bytes" "bytes"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -49,11 +48,7 @@ func checkPermsStderr() (string, error) {
} }
func TestCheckPerms(t *testing.T) { func TestCheckPerms(t *testing.T) {
tdir, err := ioutil.TempDir("", "helmtest") tdir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tdir)
tfile := filepath.Join(tdir, "testconfig") tfile := filepath.Join(tdir, "testconfig")
fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440) fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440)
if err != nil { if err != nil {

@ -53,6 +53,7 @@ type searchHubOptions struct {
searchEndpoint string searchEndpoint string
maxColWidth uint maxColWidth uint
outputFormat output.Format outputFormat output.Format
listRepoURL bool
} }
func newSearchHubCmd(out io.Writer) *cobra.Command { func newSearchHubCmd(out io.Writer) *cobra.Command {
@ -70,6 +71,8 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts") f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table") f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
f.BoolVar(&o.listRepoURL, "list-repo-url", false, "print charts repository URL")
bindOutputFlag(cmd, &o.outputFormat) bindOutputFlag(cmd, &o.outputFormat)
return cmd return cmd
@ -88,7 +91,12 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
} }
return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth)) return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL))
}
type hubChartRepo struct {
URL string `json:"url"`
Name string `json:"name"`
} }
type hubChartElement struct { type hubChartElement struct {
@ -96,14 +104,16 @@ type hubChartElement struct {
Version string `json:"version"` Version string `json:"version"`
AppVersion string `json:"app_version"` AppVersion string `json:"app_version"`
Description string `json:"description"` Description string `json:"description"`
Repository hubChartRepo `json:"repository"`
} }
type hubSearchWriter struct { type hubSearchWriter struct {
elements []hubChartElement elements []hubChartElement
columnWidth uint columnWidth uint
listRepoURL bool
} }
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter { func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL bool) *hubSearchWriter {
var elements []hubChartElement var elements []hubChartElement
for _, r := range results { for _, r := range results {
// Backwards compatibility for Monocular // Backwards compatibility for Monocular
@ -114,9 +124,9 @@ func newHubSearchWriter(results []monocular.SearchResult, endpoint string, colum
url = r.ArtifactHub.PackageURL url = r.ArtifactHub.PackageURL
} }
elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description}) elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description, hubChartRepo{URL: r.Attributes.Repo.URL, Name: r.Attributes.Repo.Name}})
} }
return &hubSearchWriter{elements, columnWidth} return &hubSearchWriter{elements, columnWidth, listRepoURL}
} }
func (h *hubSearchWriter) WriteTable(out io.Writer) error { func (h *hubSearchWriter) WriteTable(out io.Writer) error {
@ -129,10 +139,20 @@ func (h *hubSearchWriter) WriteTable(out io.Writer) error {
} }
table := uitable.New() table := uitable.New()
table.MaxColWidth = h.columnWidth table.MaxColWidth = h.columnWidth
if h.listRepoURL {
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION", "REPO URL")
} else {
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION") table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
}
for _, r := range h.elements { for _, r := range h.elements {
if h.listRepoURL {
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description, r.Repository.URL)
} else {
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description) table.AddRow(r.URL, r.Version, r.AppVersion, r.Description)
} }
}
return output.EncodeTable(out, table) return output.EncodeTable(out, table)
} }
@ -149,7 +169,7 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er
chartList := make([]hubChartElement, 0, len(h.elements)) chartList := make([]hubChartElement, 0, len(h.elements))
for _, r := range h.elements { for _, r := range h.elements {
chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description}) chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description, r.Repository})
} }
switch format { switch format {

@ -33,6 +33,8 @@ func TestSearchHubCmd(t *testing.T) {
defer ts.Close() defer ts.Close()
// The expected output has the URL to the mocked search service in it // The expected output has the URL to the mocked search service in it
// Trailing spaces are necessary to preserve in "expected" as the uitable package adds
// them during printing.
var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION
%s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend %s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend
%s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend %s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend
@ -51,6 +53,36 @@ func TestSearchHubCmd(t *testing.T) {
} }
} }
func TestSearchHubListRepoCmd(t *testing.T) {
// Setup a mock search service
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, searchResult)
}))
defer ts.Close()
// The expected output has the URL to the mocked search service in it
// Trailing spaces are necessary to preserve in "expected" as the uitable package adds
// them during printing.
var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION REPO URL
%s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.helm.sh/stable
%s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.bitnami.com
`, ts.URL, ts.URL)
testcmd := "search hub --list-repo-url --endpoint " + ts.URL + " maria"
storage := storageFixture()
_, out, err := executeActionCommandC(storage, testcmd)
if err != nil {
t.Errorf("unexpected error, %s", err)
}
if out != expected {
t.Error("expected and actual output did not match")
t.Log(out)
t.Log(expected)
}
}
func TestSearchHubOutputCompletion(t *testing.T) { func TestSearchHubOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "search hub") outputFlagCompletionTest(t, "search hub")
} }

@ -312,8 +312,9 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
} }
repoWithSlash := fmt.Sprintf("%s/", repo) repoWithSlash := fmt.Sprintf("%s/", repo)
if strings.HasPrefix(toComplete, repoWithSlash) { if strings.HasPrefix(toComplete, repoWithSlash) {
// Must complete with charts within the specified repo // Must complete with charts within the specified repo.
completions = append(completions, compListChartsOfRepo(repo, toComplete)...) // Don't filter on toComplete to allow for shell fuzzy matching
completions = append(completions, compListChartsOfRepo(repo, "")...)
noSpace = false noSpace = false
break break
} else if strings.HasPrefix(repo, toComplete) { } else if strings.HasPrefix(repo, toComplete) {
@ -325,7 +326,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug) cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
// Now handle completions for url prefixes // Now handle completions for url prefixes
for _, url := range []string{"https://\tChart URL prefix", "http://\tChart URL prefix", "file://\tChart local URL prefix"} { for _, url := range []string{"oci://\tChart OCI prefix", "https://\tChart URL prefix", "http://\tChart URL prefix", "file://\tChart local URL prefix"} {
if strings.HasPrefix(toComplete, url) { if strings.HasPrefix(toComplete, url) {
// The user already put in the full url prefix; we don't have // The user already put in the full url prefix; we don't have
// anything to add, but make sure the shell does not default // anything to add, but make sure the shell does not default

@ -53,11 +53,11 @@ of the README file
const showCRDsDesc = ` const showCRDsDesc = `
This command inspects a chart (directory, file, or URL) and displays the contents This command inspects a chart (directory, file, or URL) and displays the contents
of the CustomResourceDefintion files of the CustomResourceDefinition files
` `
func newShowCmd(out io.Writer) *cobra.Command { func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewShow(action.ShowAll) client := action.NewShowWithConfig(action.ShowAll, cfg)
showCommand := &cobra.Command{ showCommand := &cobra.Command{
Use: "show", Use: "show",
@ -198,10 +198,6 @@ func runShow(args []string, client *action.Show) (string, error) {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
if err := checkOCI(args[0]); err != nil {
return "", err
}
cp, err := client.ChartPathOptions.LocateChart(args[0], settings) cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil { if err != nil {
return "", err return "", err

@ -98,6 +98,10 @@ func TestShowVersionCompletion(t *testing.T) {
name: "completion for show version flag", name: "completion for show version flag",
cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt", golden: "output/version-comp.txt",
}, {
name: "completion for show version flag, no filter",
cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version 0.3", repoSetup),
golden: "output/version-comp.txt",
}, { }, {
name: "completion for show version flag too few args", name: "completion for show version flag too few args",
cmd: fmt.Sprintf("%s __complete show chart --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete show chart --version ''", repoSetup),

@ -74,7 +74,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
client.DryRun = true client.DryRun = true
client.ReleaseName = "RELEASE-NAME" client.ReleaseName = "release-name"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
client.ClientOnly = !validate client.ClientOnly = !validate
client.APIVersions = chartutil.VersionSet(extraAPIs) client.APIVersions = chartutil.VersionSet(extraAPIs)

@ -43,7 +43,7 @@ func TestTemplateCmd(t *testing.T) {
}, },
{ {
name: "check name template", name: "check name template",
cmd: fmt.Sprintf(`template '%s' --name-template='foobar-{{ b64enc "abc" }}-baz'`, chartPath), cmd: fmt.Sprintf(`template '%s' --name-template='foobar-{{ b64enc "abc" | lower }}-baz'`, chartPath),
golden: "output/template-name-template.txt", golden: "output/template-name-template.txt",
}, },
{ {

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

@ -1,4 +1,5 @@
HELM_BIN HELM_BIN
HELM_BURST_LIMIT
HELM_CACHE_HOME HELM_CACHE_HOME
HELM_CONFIG_HOME HELM_CONFIG_HOME
HELM_DATA_HOME HELM_DATA_HOME
@ -8,6 +9,8 @@ HELM_KUBEASGROUPS
HELM_KUBEASUSER HELM_KUBEASUSER
HELM_KUBECAFILE HELM_KUBECAFILE
HELM_KUBECONTEXT HELM_KUBECONTEXT
HELM_KUBEINSECURE_SKIP_TLS_VERIFY
HELM_KUBETLS_SERVER_NAME
HELM_KUBETOKEN HELM_KUBETOKEN
HELM_MAX_HISTORY HELM_MAX_HISTORY
HELM_NAMESPACE HELM_NAMESPACE

@ -1,4 +1,4 @@
NAME: FOOBAR NAME: foobar
LAST DEPLOYED: Fri Sep 2 22:04:05 1977 LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed

@ -0,0 +1,8 @@
==> Linting testdata/testcharts/chart-bad-requirements
[ERROR] Chart.yaml: unable to parse YAML
error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[WARNING] templates/: directory not found
[ERROR] : unable to load chart
cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
Error: 2 chart(s) linted, 1 chart(s) failed

@ -0,0 +1,4 @@
==> Linting testdata/testcharts/chart-with-only-crds
[WARNING] templates/: directory not found
1 chart(s) linted, 0 chart(s) failed

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

@ -7,7 +7,7 @@ metadata:
app: chart-with-template-lib-archive-dep app: chart-with-template-lib-archive-dep
chart: chart-with-template-lib-archive-dep-0.1.0 chart: chart-with-template-lib-archive-dep-0.1.0
heritage: Helm heritage: Helm
release: RELEASE-NAME release: release-name
name: release-name-chart-with-template-lib-archive-dep name: release-name-chart-with-template-lib-archive-dep
spec: spec:
ports: ports:
@ -16,30 +16,30 @@ spec:
targetPort: http targetPort: http
selector: selector:
app: chart-with-template-lib-archive-dep app: chart-with-template-lib-archive-dep
release: RELEASE-NAME release: release-name
type: ClusterIP type: ClusterIP
--- ---
# Source: chart-with-template-lib-archive-dep/templates/deployment.yaml # Source: chart-with-template-lib-archive-dep/templates/deployment.yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: RELEASE-NAME-chart-with-template-lib-archive-dep name: release-name-chart-with-template-lib-archive-dep
labels: labels:
app: chart-with-template-lib-archive-dep app: chart-with-template-lib-archive-dep
chart: chart-with-template-lib-archive-dep-0.1.0 chart: chart-with-template-lib-archive-dep-0.1.0
release: RELEASE-NAME release: release-name
heritage: Helm heritage: Helm
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
app: chart-with-template-lib-archive-dep app: chart-with-template-lib-archive-dep
release: RELEASE-NAME release: release-name
template: template:
metadata: metadata:
labels: labels:
app: chart-with-template-lib-archive-dep app: chart-with-template-lib-archive-dep
release: RELEASE-NAME release: release-name
spec: spec:
containers: containers:
- name: chart-with-template-lib-archive-dep - name: chart-with-template-lib-archive-dep

@ -7,7 +7,7 @@ metadata:
app: chart-with-template-lib-dep app: chart-with-template-lib-dep
chart: chart-with-template-lib-dep-0.1.0 chart: chart-with-template-lib-dep-0.1.0
heritage: Helm heritage: Helm
release: RELEASE-NAME release: release-name
name: release-name-chart-with-template-lib-dep name: release-name-chart-with-template-lib-dep
spec: spec:
ports: ports:
@ -16,30 +16,30 @@ spec:
targetPort: http targetPort: http
selector: selector:
app: chart-with-template-lib-dep app: chart-with-template-lib-dep
release: RELEASE-NAME release: release-name
type: ClusterIP type: ClusterIP
--- ---
# Source: chart-with-template-lib-dep/templates/deployment.yaml # Source: chart-with-template-lib-dep/templates/deployment.yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: RELEASE-NAME-chart-with-template-lib-dep name: release-name-chart-with-template-lib-dep
labels: labels:
app: chart-with-template-lib-dep app: chart-with-template-lib-dep
chart: chart-with-template-lib-dep-0.1.0 chart: chart-with-template-lib-dep-0.1.0
release: RELEASE-NAME release: release-name
heritage: Helm heritage: Helm
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
app: chart-with-template-lib-dep app: chart-with-template-lib-dep
release: RELEASE-NAME release: release-name
template: template:
metadata: metadata:
labels: labels:
app: chart-with-template-lib-dep app: chart-with-template-lib-dep
release: RELEASE-NAME release: release-name
spec: spec:
containers: containers:
- name: chart-with-template-lib-dep - name: chart-with-template-lib-dep

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "foobar-YWJj-baz" app.kubernetes.io/instance: "foobar-ywjj-baz"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
@ -88,7 +88,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "foobar-YWJj-baz-testconfig" name: "foobar-ywjj-baz-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -98,7 +98,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "foobar-YWJj-baz-test" name: "foobar-ywjj-baz-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -107,7 +107,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "foobar-YWJj-baz-testconfig" name: "foobar-ywjj-baz-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
@ -88,7 +88,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -98,7 +98,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-test" name: "release-name-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -107,7 +107,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -6,7 +6,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"

@ -6,7 +6,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
@ -88,7 +88,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -98,7 +98,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-test" name: "release-name-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -107,7 +107,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
@ -89,7 +89,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -99,7 +99,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-test" name: "release-name-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -108,7 +108,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -87,7 +87,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
@ -105,7 +105,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -115,7 +115,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-test" name: "release-name-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -124,7 +124,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -3,7 +3,7 @@
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-my-alpine" name: "release-name-my-alpine"
spec: spec:
containers: containers:
- name: waiter - name: waiter

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "16" kube-version/minor: "16"
kube-version/version: "v1.16.0" kube-version/version: "v1.16.0"
@ -88,7 +88,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -98,7 +98,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-test" name: "release-name-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -107,7 +107,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -70,7 +70,7 @@ metadata:
name: subchart name: subchart
labels: labels:
helm.sh/chart: "subchart-0.1.0" helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
kube-version/major: "1" kube-version/major: "1"
kube-version/minor: "20" kube-version/minor: "20"
kube-version/version: "v1.20.0" kube-version/version: "v1.20.0"
@ -88,7 +88,7 @@ spec:
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
@ -98,7 +98,7 @@ data:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "RELEASE-NAME-test" name: "release-name-test"
annotations: annotations:
"helm.sh/hook": test "helm.sh/hook": test
spec: spec:
@ -107,7 +107,7 @@ spec:
image: "alpine:latest" image: "alpine:latest"
envFrom: envFrom:
- configMapRef: - configMapRef:
name: "RELEASE-NAME-testconfig" name: "release-name-testconfig"
command: command:
- echo - echo
- "$message" - "$message"

@ -1 +1 @@
Error: found in Chart.yaml, but missing in charts/ directory: reqsubchart2 Error: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: reqsubchart2

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

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

@ -1 +1 @@
Version: v3.7 Version: v3.9

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

@ -13,7 +13,7 @@ A few tips for working with Common:
- Be careful when using functions that generate random data (like `common.fullname.unique`). - Be careful when using functions that generate random data (like `common.fullname.unique`).
They may trigger unwanted upgrades or have other side effects. They may trigger unwanted upgrades or have other side effects.
In this document, we use `RELEASE-NAME` as the name of the release. In this document, we use `release-name` as the name of the release.
## Resource Kinds ## Resource Kinds
@ -733,7 +733,7 @@ metadata:
labels: labels:
app: metadata app: metadata
heritage: "Tiller" heritage: "Tiller"
release: "RELEASE-NAME" release: "release-name"
chart: metadata-0.1.0 chart: metadata-0.1.0
first: "matt" first: "matt"
last: "butcher" last: "butcher"
@ -748,7 +748,7 @@ metadata:
labels: labels:
app: metadata app: metadata
heritage: "Tiller" heritage: "Tiller"
release: "RELEASE-NAME" release: "release-name"
chart: metadata-0.1.0 chart: metadata-0.1.0
annotations: annotations:
``` ```
@ -791,7 +791,7 @@ Example output:
```yaml ```yaml
app: labelizer app: labelizer
heritage: "Tiller" heritage: "Tiller"
release: "RELEASE-NAME" release: "release-name"
chart: labelizer-0.1.0 chart: labelizer-0.1.0
``` ```

@ -13,7 +13,7 @@ A few tips for working with Common:
- Be careful when using functions that generate random data (like `common.fullname.unique`). - Be careful when using functions that generate random data (like `common.fullname.unique`).
They may trigger unwanted upgrades or have other side effects. They may trigger unwanted upgrades or have other side effects.
In this document, we use `RELEASE-NAME` as the name of the release. In this document, we use `release-name` as the name of the release.
## Resource Kinds ## Resource Kinds
@ -733,7 +733,7 @@ metadata:
labels: labels:
app.kubernetes.io/name: metadata app.kubernetes.io/name: metadata
app.kubernetes.io/managed-by: "Helm" app.kubernetes.io/managed-by: "Helm"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
helm.sh/chart: metadata-0.1.0 helm.sh/chart: metadata-0.1.0
first: "matt" first: "matt"
last: "butcher" last: "butcher"
@ -748,7 +748,7 @@ metadata:
labels: labels:
app.kubernetes.io/name: metadata app.kubernetes.io/name: metadata
app.kubernetes.io/managed-by: "Helm" app.kubernetes.io/managed-by: "Helm"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
helm.sh/chart: metadata-0.1.0 helm.sh/chart: metadata-0.1.0
annotations: annotations:
``` ```
@ -791,7 +791,7 @@ Example output:
```yaml ```yaml
app.kubernetes.io/name: labelizer app.kubernetes.io/name: labelizer
app.kubernetes.io/managed-by: "Tiller" app.kubernetes.io/managed-by: "Tiller"
app.kubernetes.io/instance: "RELEASE-NAME" app.kubernetes.io/instance: "release-name"
helm.sh/chart: labelizer-0.1.0 helm.sh/chart: labelizer-0.1.0
``` ```

@ -49,9 +49,9 @@ version will be specified unless the '--version' flag is set.
To override values in a chart, use either the '--values' flag and pass in a file To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line, to force string or use the '--set' flag and pass configuration from the command line, to force string
values, use '--set-string'. In case a value is large and therefore values, use '--set-string'. You can use '--set-file' to set individual
you want not to use neither '--values' nor '--set', use '--set-file' to read the values from a file when the value itself is too long for the command line
single large value from file. or is dynamically generated.
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
@ -87,10 +87,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := checkOCI(args[1]); err != nil {
return err
}
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
// Fixes #7002 - Support reading values from STDIN for `upgrade` command // Fixes #7002 - Support reading values from STDIN for `upgrade` command
@ -120,6 +116,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation
instClient.SubNotes = client.SubNotes instClient.SubNotes = client.SubNotes
instClient.Description = client.Description instClient.Description = client.Description
instClient.DependencyUpdate = client.DependencyUpdate
rel, err := runInstall(args, instClient, valueOpts, out) rel, err := runInstall(args, instClient, valueOpts, out)
if err != nil { if err != nil {
@ -154,6 +151,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
if req := ch.Metadata.Dependencies; req != nil { if req := ch.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(ch, req); err != nil { if err := action.CheckDependencies(ch, req); err != nil {
err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
if client.DependencyUpdate { if client.DependencyUpdate {
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
@ -186,8 +184,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ctx := context.Background() ctx := context.Background()
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
// Handle SIGTERM // Set up channel on which to send signal notifications.
cSignal := make(chan os.Signal) // We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
cSignal := make(chan os.Signal, 2)
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-cSignal <-cSignal

@ -406,6 +406,10 @@ func TestUpgradeVersionCompletion(t *testing.T) {
name: "completion for upgrade version flag", name: "completion for upgrade version flag",
cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt", golden: "output/version-comp.txt",
}, {
name: "completion for upgrade version flag, no filter",
cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine --version 0.3", repoSetup),
golden: "output/version-comp.txt",
}, { }, {
name: "completion for upgrade version flag too few args", name: "completion for upgrade version flag too few args",
cmd: fmt.Sprintf("%s __complete upgrade releasename --version ''", repoSetup), cmd: fmt.Sprintf("%s __complete upgrade releasename --version ''", repoSetup),

180
go.mod

@ -1,48 +1,166 @@
module helm.sh/helm/v3 module helm.sh/helm/v3
go 1.16 go 1.17
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v1.1.0
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver/v3 v3.1.1 github.com/Masterminds/semver/v3 v3.1.1
github.com/Masterminds/sprig/v3 v3.2.2 github.com/Masterminds/sprig/v3 v3.2.2
github.com/Masterminds/squirrel v1.5.0 github.com/Masterminds/squirrel v1.5.3
github.com/Masterminds/vcs v1.13.1 github.com/Masterminds/vcs v1.13.3
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/containerd/containerd v1.5.7 github.com/containerd/containerd v1.6.4
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.3
github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3 github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible github.com/docker/docker v20.10.16+incompatible
github.com/evanphx/json-patch v4.11.0+incompatible github.com/evanphx/json-patch v5.6.0+incompatible
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.0 github.com/gofrs/flock v0.8.1
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/jmoiron/sqlx v1.3.4 github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.3 github.com/lib/pq v1.10.6
github.com/mattn/go-shellwords v1.0.11 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.1.1 github.com/mitchellh/copystructure v1.2.0
github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc github.com/rubenv/sql-migrate v1.1.2
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.5
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/ziutek/mymysql v1.5.4 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d k8s.io/api v0.24.1
k8s.io/api v0.22.1 k8s.io/apiextensions-apiserver v0.24.1
k8s.io/apiextensions-apiserver v0.22.1 k8s.io/apimachinery v0.24.1
k8s.io/apimachinery v0.22.1 k8s.io/apiserver v0.24.1
k8s.io/apiserver v0.22.1 k8s.io/cli-runtime v0.24.1
k8s.io/cli-runtime v0.22.1 k8s.io/client-go v0.24.1
k8s.io/client-go v0.22.1 k8s.io/klog/v2 v2.60.1
k8s.io/klog/v2 v2.9.0 k8s.io/kubectl v0.24.1
k8s.io/kubectl v0.22.1 oras.land/oras-go v1.1.1
oras.land/oras-go v0.4.0 sigs.k8s.io/yaml v1.3.0
rsc.io/letsencrypt v0.0.3 // indirect )
sigs.k8s.io/yaml v1.2.0
require (
cloud.google.com/go v0.99.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.20 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.11+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gomodule/redigo v1.8.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.15.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.24.1 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
) )

813
go.sum

File diff suppressed because it is too large Load Diff

@ -1,56 +0,0 @@
/*
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 registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/sirupsen/logrus"
orascontext "oras.land/oras-go/pkg/context"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)
// IsOCI determines whether or not a URL is to be treated as an OCI URL
func IsOCI(url string) bool {
return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme))
}
// extractChartMeta is used to extract a chart metadata from a byte array
func extractChartMeta(chartData []byte) (*chart.Metadata, error) {
ch, err := loader.LoadArchive(bytes.NewReader(chartData))
if err != nil {
return nil, err
}
return ch.Metadata, nil
}
// ctx retrieves a fresh context.
// disable verbose logging coming from ORAS (unless debug is enabled)
func ctx(out io.Writer, debug bool) context.Context {
if !debug {
return orascontext.Background()
}
ctx := orascontext.WithLoggerFromWriter(context.Background(), out)
orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel)
return ctx
}

@ -25,18 +25,14 @@ import (
) )
func TestAtomicWriteFile(t *testing.T) { func TestAtomicWriteFile(t *testing.T) {
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
testpath := filepath.Join(dir, "test") testpath := filepath.Join(dir, "test")
stringContent := "Test content" stringContent := "Test content"
reader := bytes.NewReader([]byte(stringContent)) reader := bytes.NewReader([]byte(stringContent))
mode := os.FileMode(0644) mode := os.FileMode(0644)
err = AtomicWriteFile(testpath, reader, mode) err := AtomicWriteFile(testpath, reader, mode)
if err != nil { if err != nil {
t.Errorf("AtomicWriteFile error: %s", err) t.Errorf("AtomicWriteFile error: %s", err)
} }

@ -18,6 +18,7 @@ package resolver
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -26,28 +27,27 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
// Resolver resolves dependencies from semantic version ranges to a particular version. // Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct { type Resolver struct {
chartpath string chartpath string
cachepath string cachepath string
registryClient *registry.Client
} }
// New creates a new resolver for a given chart and a given helm home. // New creates a new resolver for a given chart, helm home and registry client.
func New(chartpath, cachepath string) *Resolver { func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver {
return &Resolver{ return &Resolver{
chartpath: chartpath, chartpath: chartpath,
cachepath: cachepath, cachepath: cachepath,
registryClient: registryClient,
} }
} }
@ -135,9 +135,36 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
found = false found = false
} else { } else {
version = d.Version version = d.Version
if !FeatureGateOCI.IsEnabled() {
return nil, errors.Wrapf(FeatureGateOCI.Error(), // Check to see if an explicit version has been provided
"repository %s is an OCI registry", d.Repository) _, err := semver.NewVersion(version)
// Use an explicit version, otherwise search for tags
if err == nil {
vs = []*repo.ChartVersion{{
Metadata: &chart.Metadata{
Version: version,
},
}}
} else {
// Retrieve list of tags for repository
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
tags, err := r.registryClient.Tags(ref)
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve list of tags for repository %s", d.Repository)
}
vs = make(repo.ChartVersions, len(tags))
for ti, t := range tags {
// Mock chart version objects
version := &repo.ChartVersion{
Metadata: &chart.Metadata{
Version: t,
},
}
vs[ti] = version
}
} }
} }
@ -149,7 +176,8 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
// The version are already sorted and hence the first one to satisfy the constraint is used // The version are already sorted and hence the first one to satisfy the constraint is used
for _, ver := range vs { for _, ver := range vs {
v, err := semver.NewVersion(ver.Version) v, err := semver.NewVersion(ver.Version)
if err != nil || len(ver.URLs) == 0 { // OCI does not need URLs
if err != nil || (!registry.IsOCI(d.Repository) && len(ver.URLs) == 0) {
// Not a legit entry. // Not a legit entry.
continue continue
} }

@ -20,6 +20,7 @@ import (
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/registry"
) )
func TestResolve(t *testing.T) { func TestResolve(t *testing.T) {
@ -139,7 +140,8 @@ func TestResolve(t *testing.T) {
} }
repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"}
r := New("testdata/chartpath", "testdata/repository") registryClient, _ := registry.NewClient()
r := New("testdata/chartpath", "testdata/repository", registryClient)
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
l, err := r.Resolve(tt.req, repoNames) l, err := r.Resolve(tt.req, repoNames)

@ -71,7 +71,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "error evaluating symlink %s", path) return errors.Wrapf(err, "error evaluating symlink %s", path)
} }
log.Printf("found symbolic link in path: %s resolves to %s", path, resolved) log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved)
if info, err = os.Lstat(resolved); err != nil { if info, err = os.Lstat(resolved); err != nil {
return err return err
} }

@ -40,15 +40,6 @@ type HelperT interface {
Helper() Helper()
} }
// AssertGoldenBytes asserts that the give actual content matches the contents of the given filename
func AssertGoldenBytes(t TestingT, actual []byte, filename string) {
t.Helper()
if err := compare(actual, path(filename)); err != nil {
t.Fatalf("%v", err)
}
}
// AssertGoldenString asserts that the given string matches the contents of the given file. // AssertGoldenString asserts that the given string matches the contents of the given file.
func AssertGoldenString(t TestingT, actual, filename string) { func AssertGoldenString(t TestingT, actual, filename string) {
t.Helper() t.Helper()
@ -66,7 +57,7 @@ func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
AssertGoldenBytes(t, actual, expectedFilename) AssertGoldenString(t, string(actual), expectedFilename)
} }
func path(filename string) string { func path(filename string) string {

@ -46,13 +46,9 @@ var (
) )
func TestRenameWithFallback(t *testing.T) { func TestRenameWithFallback(t *testing.T) {
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
if err = RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil { if err := RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil {
t.Fatal("expected an error for non existing file, but got nil") t.Fatal("expected an error for non existing file, but got nil")
} }
@ -64,31 +60,27 @@ func TestRenameWithFallback(t *testing.T) {
srcf.Close() srcf.Close()
} }
if err = RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil { if err := RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil {
t.Fatal(err) t.Fatal(err)
} }
srcpath = filepath.Join(dir, "a") srcpath = filepath.Join(dir, "a")
if err = os.MkdirAll(srcpath, 0777); err != nil { if err := os.MkdirAll(srcpath, 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
dstpath := filepath.Join(dir, "b") dstpath := filepath.Join(dir, "b")
if err = os.MkdirAll(dstpath, 0777); err != nil { if err := os.MkdirAll(dstpath, 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err = RenameWithFallback(srcpath, dstpath); err == nil { if err := RenameWithFallback(srcpath, dstpath); err == nil {
t.Fatal("expected an error if dst is an existing directory, but got nil") t.Fatal("expected an error if dst is an existing directory, but got nil")
} }
} }
func TestCopyDir(t *testing.T) { func TestCopyDir(t *testing.T) {
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcdir := filepath.Join(dir, "src") srcdir := filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0755); err != nil { if err := os.MkdirAll(srcdir, 0755); err != nil {
@ -108,7 +100,7 @@ func TestCopyDir(t *testing.T) {
for i, file := range files { for i, file := range files {
fn := filepath.Join(srcdir, file.path) fn := filepath.Join(srcdir, file.path)
dn := filepath.Dir(fn) dn := filepath.Dir(fn)
if err = os.MkdirAll(dn, 0755); err != nil { if err := os.MkdirAll(dn, 0755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -189,14 +181,10 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) {
}) })
defer cleanup() defer cleanup()
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
dstdir = filepath.Join(dir, "dst") dstdir = filepath.Join(dir, "dst")
if err = CopyDir(srcdir, dstdir); err == nil { if err := CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
} }
} }
@ -218,14 +206,10 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) {
var srcdir, dstdir string var srcdir, dstdir string
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if err = os.MkdirAll(srcdir, 0755); err != nil { if err := os.MkdirAll(srcdir, 0755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -242,12 +226,9 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) {
func TestCopyDirFail_SrcIsNotDir(t *testing.T) { func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
var srcdir, dstdir string var srcdir, dstdir string
var err error
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if _, err = os.Create(srcdir); err != nil { if _, err = os.Create(srcdir); err != nil {
@ -268,12 +249,9 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
func TestCopyDirFail_DstExists(t *testing.T) { func TestCopyDirFail_DstExists(t *testing.T) {
var srcdir, dstdir string var srcdir, dstdir string
var err error
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if err = os.MkdirAll(srcdir, 0755); err != nil { if err = os.MkdirAll(srcdir, 0755); err != nil {
@ -314,14 +292,10 @@ func TestCopyDirFailOpen(t *testing.T) {
var srcdir, dstdir string var srcdir, dstdir string
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if err = os.MkdirAll(srcdir, 0755); err != nil { if err := os.MkdirAll(srcdir, 0755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -345,11 +319,7 @@ func TestCopyDirFailOpen(t *testing.T) {
} }
func TestCopyFile(t *testing.T) { func TestCopyFile(t *testing.T) {
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcf, err := os.Create(filepath.Join(dir, "srcfile")) srcf, err := os.Create(filepath.Join(dir, "srcfile"))
if err != nil { if err != nil {
@ -405,13 +375,7 @@ func cleanUpDir(dir string) {
} }
func TestCopyFileSymlink(t *testing.T) { func TestCopyFileSymlink(t *testing.T) {
var tempdir, err = ioutil.TempDir("", "gotest") tempdir := t.TempDir()
if err != nil {
t.Fatalf("failed to create directory: %s", err)
}
defer cleanUpDir(tempdir)
testcases := map[string]string{ testcases := map[string]string{
filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(tempdir, "dst-file"), filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(tempdir, "dst-file"),
@ -477,11 +441,7 @@ func TestCopyFileFail(t *testing.T) {
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
srcf, err := os.Create(filepath.Join(dir, "srcfile")) srcf, err := os.Create(filepath.Join(dir, "srcfile"))
if err != nil { if err != nil {
@ -517,11 +477,7 @@ func TestCopyFileFail(t *testing.T) {
// files this function creates. It is the caller's responsibility to call // files this function creates. It is the caller's responsibility to call
// this function before the test is done running, whether there's an error or not. // this function before the test is done running, whether there's an error or not.
func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
return nil // keep compiler happy
}
subdir := filepath.Join(dir, "dir") subdir := filepath.Join(dir, "dir")
@ -529,9 +485,6 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
if err := os.Chmod(subdir, 0777); err != nil { if err := os.Chmod(subdir, 0777); err != nil {
t.Error(err) t.Error(err)
} }
if err := os.RemoveAll(dir); err != nil {
t.Error(err)
}
} }
if err := os.Mkdir(subdir, 0777); err != nil { if err := os.Mkdir(subdir, 0777); err != nil {
@ -617,14 +570,10 @@ func TestIsSymlink(t *testing.T) {
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
dir, err := ioutil.TempDir("", "helm-tmp") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
dirPath := filepath.Join(dir, "directory") dirPath := filepath.Join(dir, "directory")
if err = os.MkdirAll(dirPath, 0777); err != nil { if err := os.MkdirAll(dirPath, 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -1,4 +1,4 @@
// +build !windows //go:build !windows
/* /*
Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under

@ -1,4 +1,4 @@
// +build windows //go:build windows
/* /*
Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under

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

@ -32,12 +32,12 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine" "helm.sh/helm/v3/pkg/engine"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage"
@ -272,6 +272,7 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
Major: kubeVersion.Major, Major: kubeVersion.Major,
Minor: kubeVersion.Minor, Minor: kubeVersion.Minor,
}, },
HelmVersion: chartutil.DefaultCapabilities.HelmVersion,
} }
return cfg.Capabilities, nil return cfg.Capabilities, nil
} }

@ -18,15 +18,14 @@ package action
import ( import (
"flag" "flag"
"io/ioutil" "io/ioutil"
"os"
"testing" "testing"
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
@ -38,13 +37,6 @@ var verbose = flag.Bool("test.log", false, "enable test logging")
func actionConfigFixture(t *testing.T) *Configuration { func actionConfigFixture(t *testing.T) *Configuration {
t.Helper() t.Helper()
tdir, err := ioutil.TempDir("", "helm-action-test")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(tdir) })
registryClient, err := registry.NewClient() registryClient, err := registry.NewClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

@ -18,7 +18,6 @@ package action
import ( import (
"bytes" "bytes"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -60,7 +59,7 @@ func TestList(t *testing.T) {
if err := NewDependency().List(tcase.chart, &buf); err != nil { if err := NewDependency().List(tcase.chart, &buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
test.AssertGoldenBytes(t, buf.Bytes(), tcase.golden) test.AssertGoldenString(t, buf.String(), tcase.golden)
} }
} }
@ -68,11 +67,7 @@ func TestList(t *testing.T) {
// chart names do not cause resolution problems. // chart names do not cause resolution problems.
func TestDependencyStatus_Dashes(t *testing.T) { func TestDependencyStatus_Dashes(t *testing.T) {
// Make a temp dir // Make a temp dir
dir, err := ioutil.TempDir("", "helmtest-") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
chartpath := filepath.Join(dir, "charts") chartpath := filepath.Join(dir, "charts")
if err := os.MkdirAll(chartpath, 0700); err != nil { if err := os.MkdirAll(chartpath, 0700); err != nil {
@ -81,7 +76,7 @@ func TestDependencyStatus_Dashes(t *testing.T) {
// Add some fake charts // Add some fake charts
first := buildChart(withName("first-chart")) first := buildChart(withName("first-chart"))
_, err = chartutil.Save(first, chartpath) _, err := chartutil.Save(first, chartpath)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -106,11 +101,7 @@ func TestDependencyStatus_Dashes(t *testing.T) {
func TestStatArchiveForStatus(t *testing.T) { func TestStatArchiveForStatus(t *testing.T) {
// Make a temp dir // Make a temp dir
dir, err := ioutil.TempDir("", "helmtest-") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
chartpath := filepath.Join(dir, "charts") chartpath := filepath.Join(dir, "charts")
if err := os.MkdirAll(chartpath, 0700); err != nil { if err := os.MkdirAll(chartpath, 0700); err != nil {

@ -38,7 +38,6 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
@ -47,6 +46,7 @@ import (
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
@ -54,13 +54,6 @@ import (
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
) )
// releaseNameMaxLen is the maximum length of a release name.
//
// As of Kubernetes 1.4, the max limit on a name is 63 chars. We reserve 10 for
// charts to add data. Effectively, that gives us 53 chars.
// See https://github.com/helm/helm/issues/1528
const releaseNameMaxLen = 53
// NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine // NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine
// but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually // but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually
// wants to see this file after rendering in the status command. However, it must be a suffix // wants to see this file after rendering in the status command. However, it must be a suffix
@ -124,13 +117,20 @@ type ChartPathOptions struct {
Username string // --username Username string // --username
Verify bool // --verify Verify bool // --verify
Version string // --version Version string // --version
// registryClient provides a registry client but is not added with
// options from a flag
registryClient *registry.Client
} }
// NewInstall creates a new Install object with the given configuration. // NewInstall creates a new Install object with the given configuration.
func NewInstall(cfg *Configuration) *Install { func NewInstall(cfg *Configuration) *Install {
return &Install{ in := &Install{
cfg: cfg, cfg: cfg,
} }
in.ChartPathOptions.registryClient = cfg.RegistryClient
return in
} }
func (i *Install) installCRDs(crds []chart.CRD) error { func (i *Install) installCRDs(crds []chart.CRD) error {
@ -198,6 +198,10 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
return nil, err return nil, err
} }
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
return nil, err
}
// Pre-install anything in the crd/ directory. We do this before Helm // Pre-install anything in the crd/ directory. We do this before Helm
// contacts the upstream server and builds the capabilities object. // contacts the upstream server and builds the capabilities object.
if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 {
@ -226,10 +230,6 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") i.cfg.Log("API Version list given outside of client only mode, this list will be ignored")
} }
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
return nil, err
}
// Make sure if Atomic is set, that wait is set as well. This makes it so // Make sure if Atomic is set, that wait is set as well. This makes it so
// the user doesn't have to specify both // the user doesn't have to specify both
i.Wait = i.Wait || i.Atomic i.Wait = i.Wait || i.Atomic
@ -344,8 +344,10 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
return rel, err return rel, err
} }
rChan := make(chan resultMessage) rChan := make(chan resultMessage)
doneChan := make(chan struct{})
defer close(doneChan)
go i.performInstall(rChan, rel, toBeAdopted, resources) go i.performInstall(rChan, rel, toBeAdopted, resources)
go i.handleContext(ctx, rChan, rel) go i.handleContext(ctx, rChan, doneChan, rel)
result := <-rChan result := <-rChan
//start preformInstall go routine //start preformInstall go routine
return result.r, result.e return result.r, result.e
@ -416,12 +418,14 @@ func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, t
i.reportToRun(c, rel, nil) i.reportToRun(c, rel, nil)
} }
func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, rel *release.Release) { func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, done chan struct{}, rel *release.Release) {
go func() { select {
<-ctx.Done() case <-ctx.Done():
err := ctx.Err() err := ctx.Err()
i.reportToRun(c, rel, err) i.reportToRun(c, rel, err)
}() case <-done:
return
}
} }
func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) { func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) {
i.Lock.Lock() i.Lock.Lock()
@ -458,14 +462,10 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release
// - used by a deleted release, and i.Replace is false // - used by a deleted release, and i.Replace is false
func (i *Install) availableName() error { func (i *Install) availableName() error {
start := i.ReleaseName start := i.ReleaseName
if start == "" {
return errors.New("name is required")
}
if len(start) > releaseNameMaxLen { if err := chartutil.ValidateReleaseName(start); err != nil {
return errors.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen) return errors.Wrapf(err, "release name %q", start)
} }
if i.DryRun { if i.DryRun {
return nil return nil
} }
@ -673,6 +673,12 @@ OUTER:
// //
// If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. // If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart.
func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) {
// If there is no registry client and the name is in an OCI registry return
// an error and a lookup will not occur.
if registry.IsOCI(name) && c.registryClient == nil {
return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name)
}
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
version := strings.TrimSpace(c.Version) version := strings.TrimSpace(c.Version)
@ -703,13 +709,11 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
}, },
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
RegistryClient: c.registryClient,
} }
if registry.IsOCI(name) { if registry.IsOCI(name) {
if version == "" { dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient))
return "", errors.New("version is explicitly required for OCI registries")
}
dl.Options = append(dl.Options, getter.WithTagName(version))
} }
if c.Verify { if c.Verify {

@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -29,6 +28,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v3/internal/test" "helm.sh/helm/v3/internal/test"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
@ -56,9 +56,12 @@ func installAction(t *testing.T) *Install {
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t)
instAction := installAction(t) instAction := installAction(t)
vals := map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(), vals) ctx, done := context.WithCancel(context.Background())
res, err := instAction.RunWithContext(ctx, buildChart(), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -77,6 +80,14 @@ func TestInstallRelease(t *testing.T) {
is.NotEqual(len(rel.Manifest), 0) is.NotEqual(len(rel.Manifest), 0)
is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
is.Equal(rel.Info.Description, "Install complete") is.Equal(rel.Info.Description, "Install complete")
// Detecting previous bug where context termination after successful release
// caused release to fail.
done()
time.Sleep(time.Millisecond * 100)
lastRelease, err := instAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
} }
func TestInstallReleaseWithValues(t *testing.T) { func TestInstallReleaseWithValues(t *testing.T) {
@ -132,7 +143,7 @@ func TestInstallRelease_NoName(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected failure when no name is specified") t.Fatal("expected failure when no name is specified")
} }
assert.Contains(t, err.Error(), "name is required") assert.Contains(t, err.Error(), "no name provided")
} }
func TestInstallRelease_WithNotes(t *testing.T) { func TestInstallRelease_WithNotes(t *testing.T) {
@ -540,15 +551,11 @@ func TestInstallReleaseOutputDir(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
vals := map[string]interface{}{} vals := map[string]interface{}{}
dir, err := ioutil.TempDir("", "output-dir") dir := t.TempDir()
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
instAction.OutputDir = dir instAction.OutputDir = dir
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
@ -576,11 +583,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
vals := map[string]interface{}{} vals := map[string]interface{}{}
dir, err := ioutil.TempDir("", "output-dir") dir := t.TempDir()
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
instAction.OutputDir = dir instAction.OutputDir = dir
instAction.UseReleaseName = true instAction.UseReleaseName = true
@ -588,7 +591,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
newDir := filepath.Join(dir, instAction.ReleaseName) newDir := filepath.Join(dir, instAction.ReleaseName)
_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }

@ -36,6 +36,7 @@ type Lint struct {
Strict bool Strict bool
Namespace string Namespace string
WithSubcharts bool WithSubcharts bool
Quiet bool
} }
// LintResult is the result of Lint // LintResult is the result of Lint
@ -75,6 +76,16 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
return result return result
} }
// HasWaringsOrErrors checks is LintResult has any warnings or errors
func HasWarningsOrErrors(result *LintResult) bool {
for _, msg := range result.Messages {
if msg.Severity > support.InfoSev {
return true
}
}
return false
}
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
var chartPath string var chartPath string
linter := support.Linter{} linter := support.Linter{}

@ -25,11 +25,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
@ -87,18 +87,14 @@ func (p *Pull) Run(chartRef string) (string, error) {
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify), getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
}, },
RegistryClient: p.cfg.RegistryClient,
RepositoryConfig: p.Settings.RepositoryConfig, RepositoryConfig: p.Settings.RepositoryConfig,
RepositoryCache: p.Settings.RepositoryCache, RepositoryCache: p.Settings.RepositoryCache,
} }
if registry.IsOCI(chartRef) { if registry.IsOCI(chartRef) {
if p.Version == "" {
return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
}
c.Options = append(c.Options, c.Options = append(c.Options,
getter.WithRegistryClient(p.cfg.RegistryClient), getter.WithRegistryClient(p.cfg.RegistryClient))
getter.WithTagName(p.Version))
} }
if p.Verify { if p.Verify {

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

Loading…
Cancel
Save