Merge branch 'main' into feat/status

pull/10336/head
Carlos Rodríguez Hernández 4 years ago committed by GitHub
commit 675c7bd366
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

@ -5,18 +5,20 @@ maintainers:
- jdolitsky - jdolitsky
- marckhouzam - marckhouzam
- mattfarina - mattfarina
- prydonius
- scottrigby - scottrigby
- SlickNik - SlickNik
- technosophos - technosophos
triage: triage:
- joejulian
- yxxhero - 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,9 +72,7 @@ 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
@ -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 postRendererOptions struct {
renderer *postrender.PostRenderer
binaryPath string
args []string
}
type postRendererString struct {
options *postRendererOptions
}
func (p *postRendererString) String() string {
return p.options.binaryPath
}
func (p *postRendererString) Type() string {
return "postRendererString"
}
func (p *postRendererString) Set(val string) error {
if val == "" {
return nil
}
p.options.binaryPath = val
pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...)
if err != nil {
return err
}
*p.options.renderer = pr
return nil
} }
type postRenderer struct { type postRendererArgsSlice struct {
renderer *postrender.PostRenderer options *postRendererOptions
} }
func (p postRenderer) String() string { func (p *postRendererArgsSlice) String() string {
return "exec" return "[" + strings.Join(p.options.args, ",") + "]"
} }
func (p postRenderer) Type() string { func (p *postRendererArgsSlice) Type() string {
return "postrenderer" return "postRendererArgsSlice"
} }
func (p postRenderer) Set(s string) error { func (p *postRendererArgsSlice) Set(val string) error {
if s == "" {
// 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 return nil
} }
pr, err := postrender.NewExec(s) // overwrite if already create PostRenderer by `post-renderer` flags
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
}
func (p *postRendererArgsSlice) Append(val string) error {
p.options.args = append(p.options.args, val)
return nil 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,24 +206,21 @@ 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 appVersion := details.Metadata.AppVersion
if strings.HasPrefix(version, toComplete) { appVersionDesc := ""
appVersion := details.Metadata.AppVersion if appVersion != "" {
appVersionDesc := "" appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
if appVersion != "" { }
appVersionDesc = fmt.Sprintf("App: %s, ", appVersion) created := details.Created.Format("January 2, 2006")
} createdDesc := ""
created := details.Created.Format("January 2, 2006") if created != "" {
createdDesc := "" createdDesc = fmt.Sprintf("Created: %s ", created)
if created != "" { }
createdDesc = fmt.Sprintf("Created: %s ", created) deprecated := ""
} if details.Metadata.Deprecated {
deprecated := "" deprecated = "(deprecated)"
if details.Metadata.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) {

@ -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) appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
if strings.HasPrefix(version, toComplete) { chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion) revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(release.Version), appVersion, chartDesc))
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", version, appVersion, chartDesc))
}
} }
return revisions, cobra.ShellCompDirectiveNoFileComp return revisions, cobra.ShellCompDirectiveNoFileComp
} }

@ -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.
@ -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),

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

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

@ -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,9 +81,7 @@ 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 {

@ -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(), Args: require.MinimumNArgs(2),
PersistentPreRunE: checkOCIFeatureGate(), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Args: require.MinimumNArgs(2), 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)
}

@ -29,11 +29,9 @@ This command consists of multiple subcommands to interact with registries.
func newRegistryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &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"
) )
@ -42,11 +41,11 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
var passwordFromStdinOpt, insecureOpt bool var passwordFromStdinOpt, insecureOpt bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "login [host]", Use: "login [host]",
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"
) )
@ -32,14 +31,14 @@ Remove credentials stored for a remote registry.
func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "logout [host]", Use: "logout [host]",
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)
}

@ -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"
@ -131,9 +130,7 @@ 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"
) )
@ -106,9 +106,7 @@ 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,9 +131,7 @@ 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
} }
@ -169,7 +165,7 @@ 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(out),
newRepoCmd(out), newRepoCmd(out),
@ -197,7 +193,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 +257,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,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
/* /*

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
/* /*

@ -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,22 +91,29 @@ 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 {
URL string `json:"url"` URL string `json:"url"`
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,9 +139,19 @@ func (h *hubSearchWriter) WriteTable(out io.Writer) error {
} }
table := uitable.New() table := uitable.New()
table.MaxColWidth = h.columnWidth table.MaxColWidth = h.columnWidth
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
if h.listRepoURL {
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION", "REPO URL")
} else {
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
}
for _, r := range h.elements { for _, r := range h.elements {
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description) 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)
}
} }
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,5 +1,4 @@
RELEASE NAME: FOOBAR NAME: foobar
CHART: empty-0.1.0
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

@ -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.8", GitCommit:"", GitTreeState:"", GoVersion:""}

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

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

@ -1 +1 @@
version.BuildInfo{Version:"v3.7", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.8", 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
@ -154,6 +150,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 +183,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),

@ -3,46 +3,45 @@ module helm.sh/helm/v3
go 1.16 go 1.16
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.4.1
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.2
github.com/Masterminds/vcs v1.13.1 github.com/Masterminds/vcs v1.13.1
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.5.9
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.12+incompatible
github.com/evanphx/json-patch v4.11.0+incompatible github.com/evanphx/json-patch v4.12.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.4
github.com/lib/pq v1.10.3 github.com/lib/pq v1.10.4
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.2
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 v0.0.0-20210614095031-55d5740dbbcc
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.3.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.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/ziutek/mymysql v1.5.4 // indirect github.com/ziutek/mymysql v1.5.4 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
k8s.io/api v0.22.1 k8s.io/api v0.23.3
k8s.io/apiextensions-apiserver v0.22.1 k8s.io/apiextensions-apiserver v0.23.3
k8s.io/apimachinery v0.22.1 k8s.io/apimachinery v0.23.3
k8s.io/apiserver v0.22.1 k8s.io/apiserver v0.23.3
k8s.io/cli-runtime v0.22.1 k8s.io/cli-runtime v0.23.3
k8s.io/client-go v0.22.1 k8s.io/client-go v0.23.3
k8s.io/klog/v2 v2.9.0 k8s.io/klog/v2 v2.30.0
k8s.io/kubectl v0.22.1 k8s.io/kubectl v0.23.3
oras.land/oras-go v0.4.0 oras.land/oras-go v1.1.0
rsc.io/letsencrypt v0.0.3 // indirect sigs.k8s.io/yaml v1.3.0
sigs.k8s.io/yaml v1.2.0
) )

456
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
}

@ -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,22 @@ 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() { // Retrieve list of tags for repository
return nil, errors.Wrapf(FeatureGateOCI.Error(), ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
"repository %s is an OCI registry", d.Repository) 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 +162,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)

@ -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.8"
// 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"

@ -23,10 +23,10 @@ import (
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,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,7 @@ 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 version == "" {
return "", errors.New("version is explicitly required for OCI registries")
}
dl.Options = append(dl.Options, getter.WithTagName(version))
} }
if c.Verify { if c.Verify {

@ -29,6 +29,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 +57,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 +81,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 +144,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) {

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

@ -19,11 +19,10 @@ package action
import ( import (
"strings" "strings"
"helm.sh/helm/v3/internal/experimental/pusher"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/internal/experimental/uploader"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/pusher"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/uploader"
) )
// Push is the action for uploading a chart. // Push is the action for uploading a chart.
@ -31,14 +30,14 @@ import (
// It provides the implementation of 'helm push'. // It provides the implementation of 'helm push'.
type Push struct { type Push struct {
Settings *cli.EnvSettings Settings *cli.EnvSettings
cfg *action.Configuration cfg *Configuration
} }
// PushOpt is a type of function that sets options for a push action. // PushOpt is a type of function that sets options for a push action.
type PushOpt func(*Push) type PushOpt func(*Push)
// WithPushConfig sets the cfg field on the push configuration object. // WithPushConfig sets the cfg field on the push configuration object.
func WithPushConfig(cfg *action.Configuration) PushOpt { func WithPushConfig(cfg *Configuration) PushOpt {
return func(p *Push) { return func(p *Push) {
p.cfg = cfg p.cfg = cfg
} }

@ -19,17 +19,16 @@ package action
import ( import (
"io" "io"
"helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/action"
) )
// RegistryLogin performs a registry login operation. // RegistryLogin performs a registry login operation.
type RegistryLogin struct { type RegistryLogin struct {
cfg *action.Configuration cfg *Configuration
} }
// NewRegistryLogin creates a new RegistryLogin object with the given configuration. // NewRegistryLogin creates a new RegistryLogin object with the given configuration.
func NewRegistryLogin(cfg *action.Configuration) *RegistryLogin { func NewRegistryLogin(cfg *Configuration) *RegistryLogin {
return &RegistryLogin{ return &RegistryLogin{
cfg: cfg, cfg: cfg,
} }

@ -18,17 +18,15 @@ package action
import ( import (
"io" "io"
"helm.sh/helm/v3/pkg/action"
) )
// RegistryLogout performs a registry login operation. // RegistryLogout performs a registry login operation.
type RegistryLogout struct { type RegistryLogout struct {
cfg *action.Configuration cfg *Configuration
} }
// NewRegistryLogout creates a new RegistryLogout object with the given configuration. // NewRegistryLogout creates a new RegistryLogout object with the given configuration.
func NewRegistryLogout(cfg *action.Configuration) *RegistryLogout { func NewRegistryLogout(cfg *Configuration) *RegistryLogout {
return &RegistryLogout{ return &RegistryLogout{
cfg: cfg, cfg: cfg,
} }

@ -64,12 +64,24 @@ type Show struct {
} }
// NewShow creates a new Show object with the given configuration. // NewShow creates a new Show object with the given configuration.
// Deprecated: Use NewShowWithConfig
// TODO Helm 4: Fold NewShowWithConfig back into NewShow
func NewShow(output ShowOutputFormat) *Show { func NewShow(output ShowOutputFormat) *Show {
return &Show{ return &Show{
OutputFormat: output, OutputFormat: output,
} }
} }
// NewShowWithConfig creates a new Show object with the given configuration.
func NewShowWithConfig(output ShowOutputFormat, cfg *Configuration) *Show {
sh := &Show{
OutputFormat: output,
}
sh.ChartPathOptions.registryClient = cfg.RegistryClient
return sh
}
// Run executes 'helm show' against the given release. // Run executes 'helm show' against the given release.
func (s *Show) Run(chartpath string) (string, error) { func (s *Show) Run(chartpath string) (string, error) {
if s.chart == nil { if s.chart == nil {

@ -23,7 +23,8 @@ import (
) )
func TestShow(t *testing.T) { func TestShow(t *testing.T) {
client := NewShow(ShowAll) config := actionConfigFixture(t)
client := NewShowWithConfig(ShowAll, config)
client.chart = &chart.Chart{ client.chart = &chart.Chart{
Metadata: &chart.Metadata{Name: "alpine"}, Metadata: &chart.Metadata{Name: "alpine"},
Files: []*chart.File{ Files: []*chart.File{

@ -112,9 +112,12 @@ type resultMessage struct {
// NewUpgrade creates a new Upgrade object with the given configuration. // NewUpgrade creates a new Upgrade object with the given configuration.
func NewUpgrade(cfg *Configuration) *Upgrade { func NewUpgrade(cfg *Configuration) *Upgrade {
return &Upgrade{ up := &Upgrade{
cfg: cfg, cfg: cfg,
} }
up.ChartPathOptions.registryClient = cfg.RegistryClient
return up
} }
// Run executes the upgrade on the given release. // Run executes the upgrade on the given release.
@ -321,11 +324,17 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
return nil, err return nil, err
} }
rChan := make(chan resultMessage) rChan := make(chan resultMessage)
ctxChan := make(chan resultMessage)
doneChan := make(chan interface{})
defer close(doneChan)
go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease) go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease)
go u.handleContext(ctx, rChan, upgradedRelease) go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease)
result := <-rChan select {
case result := <-rChan:
return result.r, result.e return result.r, result.e
case result := <-ctxChan:
return result.r, result.e
}
} }
// Function used to lock the Mutex, this is important for the case when the atomic flag is set. // Function used to lock the Mutex, this is important for the case when the atomic flag is set.
@ -341,14 +350,16 @@ func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Re
} }
// Setup listener for SIGINT and SIGTERM // Setup listener for SIGINT and SIGTERM
func (u *Upgrade) handleContext(ctx context.Context, c chan<- resultMessage, upgradedRelease *release.Release) { func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) {
select {
go func() { case <-ctx.Done():
<-ctx.Done()
err := ctx.Err() err := ctx.Err()
// when the atomic flag is set the ongoing release finish first and doesn't give time for the rollback happens. // when the atomic flag is set the ongoing release finish first and doesn't give time for the rollback happens.
u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err) u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err)
}() case <-done:
return
}
} }
func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release) { func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release) {
// pre-upgrade hooks // pre-upgrade hooks

@ -40,6 +40,33 @@ func upgradeAction(t *testing.T) *Upgrade {
return upAction return upAction
} }
func TestUpgradeRelease_Success(t *testing.T) {
is := assert.New(t)
req := require.New(t)
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "previous-release"
rel.Info.Status = release.StatusDeployed
req.NoError(upAction.cfg.Releases.Create(rel))
upAction.Wait = true
vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
done()
req.NoError(err)
is.Equal(res.Info.Status, release.StatusDeployed)
// Detecting previous bug where context termination after successful release
// caused release to fail.
time.Sleep(time.Millisecond * 100)
lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
}
func TestUpgradeRelease_Wait(t *testing.T) { func TestUpgradeRelease_Wait(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)

@ -51,7 +51,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
return nil return nil
} }
return errors.Wrap(err, "could not get information about the resource") return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info))
} }
// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.

@ -28,21 +28,21 @@ import (
helmversion "helm.sh/helm/v3/internal/version" helmversion "helm.sh/helm/v3/internal/version"
) )
const (
k8sVersionMajor = 1
k8sVersionMinor = 20
)
var ( var (
// The Kubernetes version can be set by LDFLAGS. In order to do that the value
// must be a string.
k8sVersionMajor = "1"
k8sVersionMinor = "20"
// DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). // DefaultVersionSet is the default version set, which includes only Core V1 ("v1").
DefaultVersionSet = allKnownVersions() DefaultVersionSet = allKnownVersions()
// DefaultCapabilities is the default set of capabilities. // DefaultCapabilities is the default set of capabilities.
DefaultCapabilities = &Capabilities{ DefaultCapabilities = &Capabilities{
KubeVersion: KubeVersion{ KubeVersion: KubeVersion{
Version: fmt.Sprintf("v%d.%d.0", k8sVersionMajor, k8sVersionMinor), Version: fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor),
Major: strconv.Itoa(k8sVersionMajor), Major: k8sVersionMajor,
Minor: strconv.Itoa(k8sVersionMinor), Minor: k8sVersionMinor,
}, },
APIVersions: DefaultVersionSet, APIVersions: DefaultVersionSet,
HelmVersion: helmversion.Get(), HelmVersion: helmversion.Get(),

@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) {
func TestDefaultCapabilitiesHelmVersion(t *testing.T) { func TestDefaultCapabilitiesHelmVersion(t *testing.T) {
hv := DefaultCapabilities.HelmVersion hv := DefaultCapabilities.HelmVersion
if hv.Version != "v3.7" { if hv.Version != "v3.8" {
t.Errorf("Expected default HelmVersion to be v3.7, got %q", hv.Version) t.Errorf("Expected default HelmVersion to be v3.8, got %q", hv.Version)
} }
} }

@ -40,19 +40,24 @@ var (
errMissingName = errors.New("no name provided") errMissingName = errors.New("no name provided")
// errInvalidName indicates that an invalid release name was provided // errInvalidName indicates that an invalid release name was provided
errInvalidName = errors.New(fmt.Sprintf( errInvalidName = fmt.Errorf(
"invalid release name, must match regex %s and the length must not be longer than 53", "invalid release name, must match regex %s and the length must not be longer than 53",
validName.String())) validName.String())
// errInvalidKubernetesName indicates that the name does not meet the Kubernetes // errInvalidKubernetesName indicates that the name does not meet the Kubernetes
// restrictions on metadata names. // restrictions on metadata names.
errInvalidKubernetesName = errors.New(fmt.Sprintf( errInvalidKubernetesName = fmt.Errorf(
"invalid metadata name, must match regex %s and the length must not be longer than 253", "invalid metadata name, must match regex %s and the length must not be longer than 253",
validName.String())) validName.String())
) )
const ( const (
// maxNameLen is the maximum length Helm allows for a release name // According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names)
// some resource names have a max length of 63 characters while others have a max
// length of 253 characters. As we cannot be sure the resources used in a chart, we
// therefore need to limit it to 63 chars and reserve 10 chars for additional part to name
// of the resource. The reason is that chart maintainers can use release name as part of
// the resource name (and some additional chars).
maxReleaseNameLen = 53 maxReleaseNameLen = 53
// maxMetadataNameLen is the maximum length Kubernetes allows for any name. // maxMetadataNameLen is the maximum length Kubernetes allows for any name.
maxMetadataNameLen = 253 maxMetadataNameLen = 253

@ -81,7 +81,7 @@ func New() *EnvSettings {
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"), KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")), RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
} }
@ -180,6 +180,11 @@ func (s *EnvSettings) Namespace() string {
return "default" return "default"
} }
// SetNamespace sets the namespace in the configuration
func (s *EnvSettings) SetNamespace(namespace string) {
s.namespace = namespace
}
// RESTClientGetter gets the kubeconfig from EnvSettings // RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
return s.config return s.config

@ -25,6 +25,20 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func TestSetNamespace(t *testing.T) {
settings := New()
if settings.namespace != "" {
t.Errorf("Expected empty namespace, got %s", settings.namespace)
}
settings.SetNamespace("testns")
if settings.namespace != "testns" {
t.Errorf("Expected namespace testns, got %s", settings.namespace)
}
}
func TestEnvSettings(t *testing.T) { func TestEnvSettings(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -37,9 +51,9 @@ func TestEnvSettings(t *testing.T) {
ns, kcontext string ns, kcontext string
debug bool debug bool
maxhistory int maxhistory int
kAsUser string kubeAsUser string
kAsGroups []string kubeAsGroups []string
kCaFile string kubeCaFile string
}{ }{
{ {
name: "defaults", name: "defaults",
@ -47,35 +61,35 @@ func TestEnvSettings(t *testing.T) {
maxhistory: defaultMaxHistory, maxhistory: defaultMaxHistory,
}, },
{ {
name: "with flags set", name: "with flags set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt", args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt",
ns: "myns", ns: "myns",
debug: true, debug: true,
maxhistory: defaultMaxHistory, maxhistory: defaultMaxHistory,
kAsUser: "poro", kubeAsUser: "poro",
kAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kCaFile: "/tmp/ca.crt", kubeCaFile: "/tmp/ca.crt",
}, },
{ {
name: "with envvars set", name: "with envvars set",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt"}, envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt"},
ns: "yourns", ns: "yourns",
maxhistory: 5, maxhistory: 5,
debug: true, debug: true,
kAsUser: "pikachu", kubeAsUser: "pikachu",
kAsGroups: []string{"operators", "snackeaters", "partyanimals"}, kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
kCaFile: "/tmp/ca.crt", kubeCaFile: "/tmp/ca.crt",
}, },
{ {
name: "with flags and envvars set", name: "with flags and envvars set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt", args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt"}, envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt"},
ns: "myns", ns: "myns",
debug: true, debug: true,
maxhistory: 5, maxhistory: 5,
kAsUser: "poro", kubeAsUser: "poro",
kAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kCaFile: "/my/ca.crt", kubeCaFile: "/my/ca.crt",
}, },
} }
@ -105,14 +119,14 @@ func TestEnvSettings(t *testing.T) {
if settings.MaxHistory != tt.maxhistory { if settings.MaxHistory != tt.maxhistory {
t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory) t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory)
} }
if tt.kAsUser != settings.KubeAsUser { if tt.kubeAsUser != settings.KubeAsUser {
t.Errorf("expected kAsUser %q, got %q", tt.kAsUser, settings.KubeAsUser) t.Errorf("expected kAsUser %q, got %q", tt.kubeAsUser, settings.KubeAsUser)
} }
if !reflect.DeepEqual(tt.kAsGroups, settings.KubeAsGroups) { if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) {
t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kAsGroups), len(settings.KubeAsGroups)) t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups))
} }
if tt.kCaFile != settings.KubeCaFile { if tt.kubeCaFile != settings.KubeCaFile {
t.Errorf("expected kCaFile %q, got %q", tt.kCaFile, settings.KubeCaFile) t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile)
} }
}) })
} }

@ -23,14 +23,15 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/internal/fileutil" "helm.sh/helm/v3/internal/fileutil"
"helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"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"
) )
@ -103,7 +104,8 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
name := filepath.Base(u.Path) name := filepath.Base(u.Path)
if u.Scheme == registry.OCIScheme { if u.Scheme == registry.OCIScheme {
name = fmt.Sprintf("%s-%s.tgz", name, version) idx := strings.LastIndexByte(name, ':')
name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:])
} }
destfile := filepath.Join(dest, name) destfile := filepath.Join(dest, name)
@ -139,12 +141,46 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, ver, nil return destfile, ver, nil
} }
func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) {
var tag string
var err error
// Evaluate whether an explicit version has been provided. Otherwise, determine version to use
_, errSemVer := semver.NewVersion(version)
if errSemVer == nil {
tag = version
} else {
// Retrieve list of repository tags
tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme)))
if err != nil {
return nil, err
}
if len(tags) == 0 {
return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref)
}
// Determine if version provided
// If empty, try to get the highest available tag
// If exact version, try to find it
// If semver constraint string, try to find a match
tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version)
if err != nil {
return nil, err
}
}
u.Path = fmt.Sprintf("%s:%s", u.Path, tag)
return u, err
}
// ResolveChartVersion resolves a chart reference to a URL. // ResolveChartVersion resolves a chart reference to a URL.
// //
// It returns the URL and sets the ChartDownloader's Options that can fetch // It returns the URL and sets the ChartDownloader's Options that can fetch
// the URL using the appropriate Getter. // the URL using the appropriate Getter.
// //
// A reference may be an HTTP URL, a 'reponame/chartname' reference, or a local path. // A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname'
// reference, or a local path.
// //
// A version is a SemVer string (1.2.3-beta.1+f334a6789). // A version is a SemVer string (1.2.3-beta.1+f334a6789).
// //
@ -159,6 +195,10 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
return nil, errors.Errorf("invalid chart URL format: %s", ref) return nil, errors.Errorf("invalid chart URL format: %s", ref)
} }
if registry.IsOCI(u.String()) {
return c.getOciURI(ref, version, u)
}
rf, err := loadRepoConfig(c.RepositoryConfig) rf, err := loadRepoConfig(c.RepositoryConfig)
if err != nil { if err != nil {
return u, err return u, err

@ -34,7 +34,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/internal/resolver" "helm.sh/helm/v3/internal/resolver"
"helm.sh/helm/v3/internal/third_party/dep/fs" "helm.sh/helm/v3/internal/third_party/dep/fs"
"helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/internal/urlutil"
@ -43,6 +42,7 @@ import (
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
@ -232,7 +232,7 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
// //
// This returns a lock file, which has all of the dependencies normalized to a specific version. // This returns a lock file, which has all of the dependencies normalized to a specific version.
func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
res := resolver.New(m.ChartPath, m.RepositoryCache) res := resolver.New(m.ChartPath, m.RepositoryCache, m.RegistryClient)
return res.Resolve(req, repoNames) return res.Resolve(req, repoNames)
} }
@ -332,6 +332,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
Keyring: m.Keyring, Keyring: m.Keyring,
RepositoryConfig: m.RepositoryConfig, RepositoryConfig: m.RepositoryConfig,
RepositoryCache: m.RepositoryCache, RepositoryCache: m.RepositoryCache,
RegistryClient: m.RegistryClient,
Getters: m.Getters, Getters: m.Getters,
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(username, password), getter.WithBasicAuth(username, password),
@ -343,11 +344,6 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
version := "" version := ""
if registry.IsOCI(churl) { if registry.IsOCI(churl) {
if !resolver.FeatureGateOCI.IsEnabled() {
return errors.Wrapf(resolver.FeatureGateOCI.Error(),
"the repository %s is an OCI registry", churl)
}
churl, version, err = parseOCIRef(churl) churl, version, err = parseOCIRef(churl)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not parse OCI reference") return errors.Wrapf(err, "could not parse OCI reference")
@ -404,12 +400,12 @@ func parseOCIRef(chartRef string) (string, string, error) {
func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) error { func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) error {
existsInSourceDirectory := map[string]bool{} existsInSourceDirectory := map[string]bool{}
isLocalDependency := map[string]bool{} isLocalDependency := map[string]bool{}
sourceFiles, err := ioutil.ReadDir(source) sourceFiles, err := os.ReadDir(source)
if err != nil { if err != nil {
return err return err
} }
// attempt to read destFiles; fail fast if we can't // attempt to read destFiles; fail fast if we can't
destFiles, err := ioutil.ReadDir(dest) destFiles, err := os.ReadDir(dest)
if err != nil { if err != nil {
return err return err
} }
@ -440,7 +436,7 @@ func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) er
} }
fmt.Fprintln(m.Out, "Deleting outdated charts") fmt.Fprintln(m.Out, "Deleting outdated charts")
// find all files that exist in dest that do not exist in source; delete them (outdated dependendencies) // find all files that exist in dest that do not exist in source; delete them (outdated dependencies)
for _, file := range destFiles { for _, file := range destFiles {
if !file.IsDir() && !existsInSourceDirectory[file.Name()] { if !file.IsDir() && !existsInSourceDirectory[file.Name()] {
fname := filepath.Join(dest, file.Name()) fname := filepath.Join(dest, file.Name())
@ -578,8 +574,7 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
missing := []string{} missing := []string{}
for _, dd := range deps { for _, dd := range deps {
// Don't map the repository, we don't need to download chart from charts directory // Don't map the repository, we don't need to download chart from charts directory
// When OCI is used there is no Helm repository if dd.Repository == "" {
if dd.Repository == "" || registry.IsOCI(dd.Repository) {
continue continue
} }
// if dep chart is from local path, verify the path is valid // if dep chart is from local path, verify the path is valid

@ -22,8 +22,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/registry"
) )
// options are generic parameters to be provided to the getter during instantiation. // options are generic parameters to be provided to the getter during instantiation.

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/pkg/registry"
) )
// OCIGetter is the default HTTP(/S) backend handler // OCIGetter is the default HTTP(/S) backend handler
@ -28,7 +28,7 @@ type OCIGetter struct {
opts options opts options
} }
//Get performs a Get from repo.Getter and returns the body. // Get performs a Get from repo.Getter and returns the body.
func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options { for _, opt := range options {
opt(&g.opts) opt(&g.opts)
@ -50,10 +50,6 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
registry.PullOptWithProv(true)) registry.PullOptWithProv(true))
} }
if version := g.opts.version; version != "" {
ref = fmt.Sprintf("%s:%s", ref, version)
}
result, err := client.Pull(ref, pullOpts...) result, err := client.Pull(ref, pullOpts...)
if err != nil { if err != nil {
return nil, err return nil, err

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !windows
// +build !windows // +build !windows
package helmpath package helmpath

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build darwin
// +build darwin // +build darwin
package helmpath package helmpath

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build darwin
// +build darwin // +build darwin
package helmpath package helmpath

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !windows && !darwin
// +build !windows,!darwin // +build !windows,!darwin
package helmpath package helmpath

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

Loading…
Cancel
Save