Shaan Satsangi 2 days ago committed by GitHub
commit 73fa4d9fd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -28,6 +28,29 @@ import (
"helm.sh/helm/v4/pkg/release"
)
// ReleaseSourceAnnotation is the chart metadata annotation under which Helm
// records the location a release's chart was installed or upgraded from, so it
// can be surfaced by 'helm list --show-source' and 'helm get metadata'.
const ReleaseSourceAnnotation = "meta.helm.sh/release-source"
// setChartSourceAnnotation records source on the chart's metadata under
// ReleaseSourceAnnotation. It is a no-op when source is empty. Callers must
// invoke it only after render values have been computed/rendered, so the
// annotation is persisted on the stored release without leaking into the
// template render context via .Chart.Annotations.
func setChartSourceAnnotation(chrt *chart.Chart, source string) {
if source == "" {
return
}
if chrt.Metadata == nil {
chrt.Metadata = &chart.Metadata{}
}
if chrt.Metadata.Annotations == nil {
chrt.Metadata.Annotations = make(map[string]string)
}
chrt.Metadata.Annotations[ReleaseSourceAnnotation] = source
}
// GetMetadata is the action for checking a given release's metadata.
//
// It provides the implementation of 'helm get metadata'.
@ -42,7 +65,14 @@ type Metadata struct {
Chart string `json:"chart" yaml:"chart"`
Version string `json:"version" yaml:"version"`
AppVersion string `json:"appVersion" yaml:"appVersion"`
// Annotations are fetched from the Chart.yaml file
// Source is where the chart was installed or upgraded from (a repository URL
// or chart reference), recorded via the meta.helm.sh/release-source
// annotation. Empty for releases created before this was recorded.
Source string `json:"source,omitempty" yaml:"source,omitempty"`
// Annotations are the chart metadata annotations from Chart.yaml. For a
// release installed or upgraded with a recorded source, this map also
// includes the meta.helm.sh/release-source annotation Helm adds at
// install/upgrade time (also surfaced separately via Source above).
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
// Labels of the release which are stored in driver metadata fields storage
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
@ -98,6 +128,7 @@ func (g *GetMetadata) Run(name string) (*Metadata, error) {
Chart: chrt.Metadata.Name,
Version: chrt.Metadata.Version,
AppVersion: chrt.Metadata.AppVersion,
Source: chrt.Metadata.Annotations[ReleaseSourceAnnotation],
Dependencies: ac.MetaDependencies(),
Annotations: chrt.Metadata.Annotations,
Labels: rac.Labels(),

@ -328,6 +328,71 @@ func TestGetMetadata_Run_WithAnnotations(t *testing.T) {
assert.Equal(t, "test-value", result.Annotations["custom.annotation"])
}
func TestGetMetadata_Run_WithSource(t *testing.T) {
cfg := actionConfigFixture(t)
client := NewGetMetadata(cfg)
releaseName := "test-release"
deployedTime := time.Now()
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "v1.2.3",
Annotations: map[string]string{
ReleaseSourceAnnotation: "https://charts.example.com",
},
},
},
Version: 1,
Namespace: "default",
}
require.NoError(t, cfg.Releases.Create(rel))
result, err := client.Run(releaseName)
require.NoError(t, err)
assert.Equal(t, "https://charts.example.com", result.Source)
}
func TestGetMetadata_Run_SourceEmptyWhenAnnotationAbsent(t *testing.T) {
cfg := actionConfigFixture(t)
client := NewGetMetadata(cfg)
releaseName := "test-release"
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: common.StatusDeployed,
LastDeployed: time.Now(),
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "v1.2.3",
},
},
Version: 1,
Namespace: "default",
}
require.NoError(t, cfg.Releases.Create(rel))
result, err := client.Run(releaseName)
require.NoError(t, err)
assert.Empty(t, result.Source)
}
func TestGetMetadata_Run_SpecificVersion(t *testing.T) {
cfg := actionConfigFixture(t)
client := NewGetMetadata(cfg)

@ -76,6 +76,13 @@ type Install struct {
ChartPathOptions
// ChartSource records where the chart was obtained from (a repository URL or
// a local chart reference). When non-empty it is persisted on the release's
// chart metadata under ReleaseSourceAnnotation after rendering, so it is
// surfaced by 'helm list --show-source' and 'helm get metadata' without ever
// being exposed to templates via .Chart.Annotations.
ChartSource string
// ForceReplace will, if set to `true`, ignore certain warnings and perform the install anyway.
//
// This should be used with caution.
@ -387,6 +394,12 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
return rel, err
}
// Record the chart source on the persisted release. This is done after
// rendering so the annotation is not visible to templates via
// .Chart.Annotations, while still being stored on rel.Chart (which shares
// the same chart pointer) for 'helm list'/'helm get metadata'.
setChartSourceAnnotation(chrt, i.ChartSource)
// Mark this release as in-progress
rel.SetStatus(rcommon.StatusPendingInstall, "Initial install underway")

@ -220,6 +220,72 @@ func TestInstallRelease(t *testing.T) {
is.Equal(lrel.Info.Status, rcommon.StatusDeployed)
}
func TestInstallReleaseRecordsChartSourceAfterRender(t *testing.T) {
is := assert.New(t)
req := require.New(t)
instAction := installAction(t)
instAction.ChartSource = "https://charts.example.com/repo"
// This template renders the chart's annotations. If the source were recorded
// before rendering, it would leak into the manifest via .Chart.Annotations.
tmpl := []*common.File{
{Name: "templates/cfg", ModTime: time.Now(), Data: []byte("annotations: {{ .Chart.Annotations }}")},
}
resi, err := instAction.RunWithContext(t.Context(), buildChartWithTemplates(tmpl), map[string]any{})
req.NoError(err)
res, err := releaserToV1Release(resi)
req.NoError(err)
// The rendered manifest must not contain the recorded source: the annotation
// is applied only after rendering, so it never reaches .Chart.Annotations.
is.NotContains(res.Manifest, "https://charts.example.com/repo")
is.NotContains(res.Manifest, ReleaseSourceAnnotation)
// The persisted release, however, must carry the source annotation so it can
// be surfaced by 'helm list --show-source' and 'helm get metadata'.
stored, err := instAction.cfg.Releases.Get(res.Name, res.Version)
req.NoError(err)
rel, err := releaserToV1Release(stored)
req.NoError(err)
is.Equal("https://charts.example.com/repo", rel.Chart.Metadata.Annotations[ReleaseSourceAnnotation])
}
func TestUpgradeReleaseRecordsChartSourceAfterRender(t *testing.T) {
is := assert.New(t)
req := require.New(t)
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "previous-release"
rel.Info.Status = rcommon.StatusDeployed
req.NoError(upAction.cfg.Releases.Create(rel))
upAction.ChartSource = "https://charts.example.com/repo"
// Same probe as the install path: a template that renders the chart's
// annotations. If the source were recorded before rendering, it would leak
// into the manifest via .Chart.Annotations.
tmpl := []*common.File{
{Name: "templates/cfg", ModTime: time.Now(), Data: []byte("annotations: {{ .Chart.Annotations }}")},
}
resi, err := upAction.RunWithContext(t.Context(), rel.Name, buildChartWithTemplates(tmpl), map[string]any{})
req.NoError(err)
res, err := releaserToV1Release(resi)
req.NoError(err)
// The recorded source must not appear in the rendered manifest.
is.NotContains(res.Manifest, "https://charts.example.com/repo")
is.NotContains(res.Manifest, ReleaseSourceAnnotation)
// The persisted upgraded release must carry the source annotation.
stored, err := upAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
last, err := releaserToV1Release(stored)
req.NoError(err)
is.Equal("https://charts.example.com/repo", last.Chart.Metadata.Annotations[ReleaseSourceAnnotation])
}
func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) {
// This test will test checking ownership of a resource
// returned by the fake client. If the resource is not

@ -52,6 +52,13 @@ type Upgrade struct {
ChartPathOptions
// ChartSource records where the chart was obtained from (a repository URL or
// a local chart reference). When non-empty it is persisted on the upgraded
// release's chart metadata under ReleaseSourceAnnotation after rendering, so
// it is surfaced by 'helm list --show-source' and 'helm get metadata'
// without ever being exposed to templates via .Chart.Annotations.
ChartSource string
// Install is a purely informative flag that indicates whether this upgrade was done in "install" mode.
//
// Applications may use this to determine whether this Upgrade operation was done as part of a
@ -305,6 +312,13 @@ func (u *Upgrade) prepareUpgrade(ctx context.Context, name string, chart *chartv
return nil, nil, false, err
}
// Record the chart source on the persisted release. This is done after
// rendering so the annotation is not visible to templates via
// .Chart.Annotations, while still being stored on the upgraded release's
// chart (which shares the same chart pointer) for 'helm list'/'helm get
// metadata'.
setChartSourceAnnotation(chart, u.ChartSource)
if driver.ContainsSystemLabels(u.Labels) {
return nil, nil, false, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())
}

@ -94,6 +94,9 @@ func (w metadataWriter) WriteTable(out io.Writer) error {
_, _ = fmt.Fprintf(out, "CHART: %v\n", w.metadata.Chart)
_, _ = fmt.Fprintf(out, "VERSION: %v\n", w.metadata.Version)
_, _ = fmt.Fprintf(out, "APP_VERSION: %v\n", w.metadata.AppVersion)
if w.metadata.Source != "" {
_, _ = fmt.Fprintf(out, "SOURCE: %v\n", w.metadata.Source)
}
_, _ = fmt.Fprintf(out, "ANNOTATIONS: %v\n", k8sLabels.Set(w.metadata.Annotations).String())
_, _ = fmt.Fprintf(out, "LABELS: %v\n", k8sLabels.Set(w.metadata.Labels).String())
_, _ = fmt.Fprintf(out, "DEPENDENCIES: %v\n", w.metadata.FormattedDepNames())

@ -19,15 +19,40 @@ package cmd
import (
"testing"
"helm.sh/helm/v4/pkg/action"
release "helm.sh/helm/v4/pkg/release/v1"
)
// mockReleaseWithSource returns a mock release whose chart records a
// meta.helm.sh/release-source annotation, so the 'get metadata' output for the
// new SOURCE field can be exercised.
func mockReleaseWithSource(name string) *release.Release {
rel := release.Mock(&release.MockReleaseOptions{Name: name, Labels: map[string]string{"key1": "value1"}})
rel.Chart.Metadata.Annotations[action.ReleaseSourceAnnotation] = "https://charts.example.com"
return rel
}
func TestGetMetadataCmd(t *testing.T) {
tests := []cmdTestCase{{
name: "get metadata with a release",
cmd: "get metadata thomas-guide",
golden: "output/get-metadata.txt",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide", Labels: map[string]string{"key1": "value1"}})},
}, {
name: "get metadata with a recorded source",
cmd: "get metadata thomas-guide",
golden: "output/get-metadata-source.txt",
rels: []*release.Release{mockReleaseWithSource("thomas-guide")},
}, {
name: "get metadata with a recorded source to json",
cmd: "get metadata thomas-guide --output json",
golden: "output/get-metadata-source.json",
rels: []*release.Release{mockReleaseWithSource("thomas-guide")},
}, {
name: "get metadata with a recorded source to yaml",
cmd: "get metadata thomas-guide --output yaml",
golden: "output/get-metadata-source.yaml",
rels: []*release.Release{mockReleaseWithSource("thomas-guide")},
}, {
name: "get metadata requires release name arg",
cmd: "get metadata",

@ -344,6 +344,12 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
cancel()
}()
// Record where the chart was installed from so 'helm list --show-source' can
// surface it later. The action applies this after rendering, so it is never
// exposed to templates via .Chart.Annotations. Resolved after any
// dependency-update reload above so it reflects the chart actually installed.
client.ChartSource = chartSource(chartRef, client.RepoURL)
ri, err := client.RunWithContext(ctx, chartRequested, vals)
rel, rerr := releaserToV1Release(ri)
if rerr != nil {

@ -65,6 +65,7 @@ flag with the '--offset' flag allows you to page through results.
func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewList(cfg)
var outfmt output.Format
var showSource bool
cmd := &cobra.Command{
Use: "list",
@ -113,7 +114,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
}
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders, settings.ShouldDisableColor()))
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders, settings.ShouldDisableColor(), showSource))
},
}
@ -134,6 +135,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.IntVar(&client.Offset, "offset", 0, "next release index in the list, used to offset from start value")
f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.")
f.BoolVar(&showSource, "show-source", false, "add a SOURCE column to the table output showing where each release's chart was installed from (chart reference or repository URL); this flag affects only table output, while JSON/YAML output includes the 'source' field whenever a release has a recorded source")
bindOutputFlag(cmd, &outfmt)
return cmd
@ -147,18 +149,24 @@ type releaseElement struct {
Status string `json:"status"`
Chart string `json:"chart"`
AppVersion string `json:"app_version"`
Source string `json:"source,omitempty"`
}
type releaseListWriter struct {
releases []releaseElement
noHeaders bool
noColor bool
releases []releaseElement
noHeaders bool
noColor bool
showSource bool
}
func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool, noColor bool) *releaseListWriter {
func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool, noColor bool, showSource bool) *releaseListWriter {
// Initialize the array so no results returns an empty array instead of null
elements := make([]releaseElement, 0, len(releases))
for _, r := range releases {
source := ""
if r.Chart != nil && r.Chart.Metadata != nil && r.Chart.Metadata.Annotations != nil {
source = r.Chart.Metadata.Annotations[action.ReleaseSourceAnnotation]
}
element := releaseElement{
Name: r.Name,
Namespace: r.Namespace,
@ -166,6 +174,7 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string, noHead
Status: r.Info.Status.String(),
Chart: formatChartName(r.Chart),
AppVersion: formatAppVersion(r.Chart),
Source: source,
}
t := "-"
@ -180,13 +189,13 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string, noHead
elements = append(elements, element)
}
return &releaseListWriter{elements, noHeaders, noColor}
return &releaseListWriter{elements, noHeaders, noColor, showSource}
}
func (w *releaseListWriter) WriteTable(out io.Writer) error {
table := uitable.New()
if !w.noHeaders {
table.AddRow(
headers := []interface{}{
coloroutput.ColorizeHeader("NAME", w.noColor),
coloroutput.ColorizeHeader("NAMESPACE", w.noColor),
coloroutput.ColorizeHeader("REVISION", w.noColor),
@ -194,7 +203,11 @@ func (w *releaseListWriter) WriteTable(out io.Writer) error {
coloroutput.ColorizeHeader("STATUS", w.noColor),
coloroutput.ColorizeHeader("CHART", w.noColor),
coloroutput.ColorizeHeader("APP VERSION", w.noColor),
)
}
if w.showSource {
headers = append(headers, coloroutput.ColorizeHeader("SOURCE", w.noColor))
}
table.AddRow(headers...)
}
for _, r := range w.releases {
// Parse the status string back to a release.Status to use color
@ -221,7 +234,11 @@ func (w *releaseListWriter) WriteTable(out io.Writer) error {
default:
status = common.Status(r.Status)
}
table.AddRow(r.Name, coloroutput.ColorizeNamespace(r.Namespace, w.noColor), r.Revision, r.Updated, coloroutput.ColorizeStatus(status, w.noColor), r.Chart, r.AppVersion)
row := []interface{}{r.Name, coloroutput.ColorizeNamespace(r.Namespace, w.noColor), r.Revision, r.Updated, coloroutput.ColorizeStatus(status, w.noColor), r.Chart, r.AppVersion}
if w.showSource {
row = append(row, r.Source)
}
table.AddRow(row...)
}
return output.EncodeTable(out, table)
}

@ -38,6 +38,9 @@ func TestListCmd(t *testing.T) {
Name: "chickadee",
Version: "1.0.0",
AppVersion: "0.0.1",
Annotations: map[string]string{
"meta.helm.sh/release-source": "https://example.com/repo",
},
},
}
@ -234,6 +237,11 @@ func TestListCmd(t *testing.T) {
cmd: "list -n milano",
golden: "output/list-namespace.txt",
rels: releaseFixture,
}, {
name: "list releases with the source column",
cmd: "list --show-source",
golden: "output/list-show-source.txt",
rels: releaseFixture,
}}
runTestCmd(t, tests)
}
@ -254,6 +262,9 @@ func TestListOutputFormats(t *testing.T) {
Name: "test-chart",
Version: "1.0.0",
AppVersion: "0.0.1",
Annotations: map[string]string{
"meta.helm.sh/release-source": "https://example.com/repo",
},
},
}
@ -313,6 +324,7 @@ func TestReleaseListWriter(t *testing.T) {
timeFormat string
noHeaders bool
noColor bool
showSource bool
}{
{
name: "empty releases list",
@ -342,11 +354,19 @@ func TestReleaseListWriter(t *testing.T) {
noHeaders: false,
noColor: true,
},
{
name: "show source",
releases: releases,
timeFormat: "",
noHeaders: false,
noColor: false,
showSource: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
writer := newReleaseListWriter(tt.releases, tt.timeFormat, tt.noHeaders, tt.noColor)
writer := newReleaseListWriter(tt.releases, tt.timeFormat, tt.noHeaders, tt.noColor, tt.showSource)
if writer == nil {
t.Error("Expected writer to be non-nil")
@ -423,7 +443,7 @@ func TestReleaseListWriterMethods(t *testing.T) {
},
}
writer := newReleaseListWriter(testReleases, "", false, false)
writer := newReleaseListWriter(testReleases, "", false, false, false)
var buf []byte
out := &bytesWriter{buf: &buf}
@ -445,7 +465,7 @@ func TestReleaseListWriterMethods(t *testing.T) {
})
}
writer := newReleaseListWriter(releases, "", false, false)
writer := newReleaseListWriter(releases, "", false, false, false)
var buf []byte
out := &bytesWriter{buf: &buf}
@ -604,7 +624,7 @@ func TestListStatusMapping(t *testing.T) {
},
}
writer := newReleaseListWriter(releaseFixture, "", false, false)
writer := newReleaseListWriter(releaseFixture, "", false, false, false)
if len(writer.releases) != 1 {
t.Errorf("Expected 1 release, got %d", len(writer.releases))
}

@ -0,0 +1,56 @@
/*
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 cmd
import (
"net/url"
)
// chartSource resolves the location a chart was installed or upgraded from, for
// persistence under action.ReleaseSourceAnnotation. A repository URL (when
// supplied) is preferred over the local chart reference, and any embedded
// credentials are stripped before the value is returned. The result is handed
// to the install/upgrade action, which records it on the release after
// rendering so it is never exposed to templates via .Chart.Annotations.
func chartSource(chartRef, repoURL string) string {
source := chartRef
if repoURL != "" {
source = repoURL
}
return sanitizeChartSource(source)
}
// sanitizeChartSource strips any user credentials and other potentially
// sensitive components (query string, fragment) from a chart source URL before
// it is persisted into the release record, so secrets embedded in a repo URL
// (e.g. https://user:pass@host) are not leaked into cluster metadata. Sources
// that are not URLs (local paths or chart references) are returned unchanged.
//
// Only sources with a host component (i.e. scheme://host/...) are treated as
// URLs. This deliberately excludes local filesystem paths, including Windows
// absolute paths such as C:\charts\mychart, which url.Parse would otherwise
// interpret as a "c" scheme and re-encode (e.g. c:%5Ccharts%5Cmychart).
func sanitizeChartSource(source string) string {
u, err := url.Parse(source)
if err != nil || u.Scheme == "" || u.Host == "" {
return source
}
u.User = nil
u.RawQuery = ""
u.Fragment = ""
return u.String()
}

@ -0,0 +1,101 @@
/*
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 cmd
import "testing"
func TestSanitizeChartSource(t *testing.T) {
tests := []struct {
name string
source string
want string
}{
{
name: "strips userinfo from https URL",
source: "https://user:pass@charts.example.com/mychart",
want: "https://charts.example.com/mychart",
},
{
name: "strips username-only userinfo",
source: "https://user@charts.example.com/mychart",
want: "https://charts.example.com/mychart",
},
{
name: "strips query string and fragment",
source: "https://charts.example.com/index.yaml?token=secret#section",
want: "https://charts.example.com/index.yaml",
},
{
name: "strips userinfo, query and fragment together",
source: "https://user:pass@charts.example.com/index.yaml?token=secret#frag",
want: "https://charts.example.com/index.yaml",
},
{
name: "strips userinfo from oci URL",
source: "oci://user:pass@registry.example.com/charts/mychart",
want: "oci://registry.example.com/charts/mychart",
},
{
name: "leaves credential-free https URL unchanged",
source: "https://charts.example.com/mychart",
want: "https://charts.example.com/mychart",
},
{
name: "leaves chart reference unchanged",
source: "stable/mychart",
want: "stable/mychart",
},
{
name: "leaves relative local path unchanged",
source: "./charts/mychart",
want: "./charts/mychart",
},
{
name: "leaves absolute unix path unchanged",
source: "/var/charts/mychart",
want: "/var/charts/mychart",
},
{
name: "leaves windows absolute path unchanged",
source: `C:\charts\mychart`,
want: `C:\charts\mychart`,
},
{
name: "leaves windows forward-slash path unchanged",
source: "C:/charts/mychart",
want: "C:/charts/mychart",
},
{
name: "leaves bare chart name unchanged",
source: "mychart",
want: "mychart",
},
{
name: "leaves empty source unchanged",
source: "",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := sanitizeChartSource(tt.source); got != tt.want {
t.Errorf("sanitizeChartSource(%q) = %q, want %q", tt.source, got, tt.want)
}
})
}
}

@ -0,0 +1 @@
{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","source":"https://charts.example.com","annotations":{"category":"web-apps","meta.helm.sh/release-source":"https://charts.example.com","supported":"true"},"labels":{"key1":"value1"},"dependencies":[{"name":"cool-plugin","version":"1.0.0","repository":"https://coolplugin.io/charts","condition":"coolPlugin.enabled","enabled":true},{"name":"crds","version":"2.7.1","repository":"","condition":"crds.enabled"}],"namespace":"default","revision":1,"status":"deployed","deployedAt":"1977-09-02T22:04:05Z"}

@ -0,0 +1,13 @@
NAME: thomas-guide
CHART: foo
VERSION: 0.1.0-beta.1
APP_VERSION: 1.0
SOURCE: https://charts.example.com
ANNOTATIONS: category=web-apps,meta.helm.sh/release-source=https://charts.example.com,supported=true
LABELS: key1=value1
DEPENDENCIES: cool-plugin,crds
NAMESPACE: default
REVISION: 1
STATUS: deployed
DEPLOYED_AT: 1977-09-02T22:04:05Z
APPLY_METHOD: client-side apply (defaulted)

@ -0,0 +1,25 @@
annotations:
category: web-apps
meta.helm.sh/release-source: https://charts.example.com
supported: "true"
appVersion: "1.0"
chart: foo
dependencies:
- condition: coolPlugin.enabled
enabled: true
name: cool-plugin
repository: https://coolplugin.io/charts
version: 1.0.0
- condition: crds.enabled
name: crds
repository: ""
version: 2.7.1
deployedAt: "1977-09-02T22:04:05Z"
labels:
key1: value1
name: thomas-guide
namespace: default
revision: 1
source: https://charts.example.com
status: deployed
version: 0.1.0-beta.1

@ -1 +1 @@
[{"name":"test-release","namespace":"default","revision":"1","updated":"2016-01-16 00:00:00 +0000 UTC","status":"deployed","chart":"test-chart-1.0.0","app_version":"0.0.1"}]
[{"name":"test-release","namespace":"default","revision":"1","updated":"2016-01-16 00:00:00 +0000 UTC","status":"deployed","chart":"test-chart-1.0.0","app_version":"0.0.1","source":"https://example.com/repo"}]

@ -0,0 +1,9 @@
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION SOURCE
drax default 1 2016-01-16 00:00:01 +0000 UTC uninstalling chickadee-1.0.0 0.0.1 https://example.com/repo
gamora default 1 2016-01-16 00:00:01 +0000 UTC superseded chickadee-1.0.0 0.0.1 https://example.com/repo
groot default 1 2016-01-16 00:00:01 +0000 UTC uninstalled chickadee-1.0.0 0.0.1 https://example.com/repo
hummingbird default 1 2016-01-16 00:00:03 +0000 UTC deployed chickadee-1.0.0 0.0.1 https://example.com/repo
iguana default 2 2016-01-16 00:00:04 +0000 UTC deployed chickadee-1.0.0 0.0.1 https://example.com/repo
rocket default 1 2016-01-16 00:00:02 +0000 UTC failed chickadee-1.0.0 0.0.1 https://example.com/repo
starlord default 2 2016-01-16 00:00:01 +0000 UTC deployed chickadee-1.0.0 0.0.1 https://example.com/repo
thanos default 1 2016-01-16 00:00:01 +0000 UTC pending-install chickadee-1.0.0 0.0.1 https://example.com/repo

@ -3,5 +3,6 @@
name: test-release
namespace: default
revision: "1"
source: https://example.com/repo
status: deployed
updated: 2016-01-16 00:00:00 +0000 UTC

@ -250,6 +250,12 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cancel()
}()
// Record where the chart was upgraded from so 'helm list --show-source'
// can surface it later. The action applies this after rendering, so it
// is never exposed to templates via .Chart.Annotations. Resolved after
// any dependency-update reload above so it reflects the actual chart.
client.ChartSource = chartSource(args[1], client.RepoURL)
rel, err := client.RunWithContext(ctx, args[0], ch, vals)
if err != nil {
return fmt.Errorf("UPGRADE FAILED: %w", err)

Loading…
Cancel
Save