Repository: helm/helm
Pull request #13343 (2024-09-19 20:51:26)

Add annotations and dependencies to get metadata output
Closes https://github.com/helm/helm/issues/12686

Sample outputs for empty annotations/dependencies..
TABLE:
```yaml
CHART: foo
VERSION: 0.1.0-beta.1
APP_VERSION: 1.0
ANNOTATIONS: 
DEPENDENCIES: 
NAMESPACE: default
REVISION: 1
STATUS: deployed
```
YAML:
```yaml
appVersion: 1.0
chart: foo
deployedAt: "2024-10-04T10:55:24+05:30"
name: foo
namespace: default
revision: 1
status: deployed
version: 0.1.0-beta.1
```
JSON:
```json
{"name":"foo","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","namespace":"default","revision":1,"status":"deployed","deployedAt":"2024-10-04T11:25:10+05:30"}
```
**N.B.:** The JSON and YAML outputs have omitempty

**What this PR does / why we need it:**
The output of helm get metadata includes a subset of the fields contained in the chart.Metadata struct. This change adds the values of the annotations field and the dependencies field to the output.

**If applicable:**
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [x] this PR has been tested for backwards compatibility


Patch:
diff --git a/cmd/helm/get_metadata.go b/cmd/helm/get_metadata.go
index dbc7b6eff..e8ad832bd 100644
--- a/cmd/helm/get_metadata.go
+++ b/cmd/helm/get_metadata.go
@@ -22,6 +22,7 @@ import (
 	"log"
 
 	"github.com/spf13/cobra"
+	k8sLabels "k8s.io/apimachinery/pkg/labels"
 
 	"helm.sh/helm/v3/cmd/helm/require"
 	"helm.sh/helm/v3/pkg/action"
@@ -78,10 +79,13 @@ 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)
+	_, _ = fmt.Fprintf(out, "ANNOTATIONS: %v\n", k8sLabels.Set(w.metadata.Annotations).String())
+	_, _ = fmt.Fprintf(out, "DEPENDENCIES: %v\n", w.metadata.FormattedDepNames())
 	_, _ = fmt.Fprintf(out, "NAMESPACE: %v\n", w.metadata.Namespace)
 	_, _ = fmt.Fprintf(out, "REVISION: %v\n", w.metadata.Revision)
 	_, _ = fmt.Fprintf(out, "STATUS: %v\n", w.metadata.Status)
 	_, _ = fmt.Fprintf(out, "DEPLOYED_AT: %v\n", w.metadata.DeployedAt)
+
 	return nil
 }
 
diff --git a/pkg/action/get_metadata.go b/pkg/action/get_metadata.go
index ec096ae16..f79788c3b 100644
--- a/pkg/action/get_metadata.go
+++ b/pkg/action/get_metadata.go
@@ -16,7 +16,13 @@ limitations under the License.
 
 package action
 
-import "time"
+import (
+	"sort"
+	"strings"
+	"time"
+
+	"helm.sh/helm/v3/pkg/chart"
+)
 
 // GetMetadata is the action for checking a given release's metadata.
 //
@@ -28,14 +34,16 @@ type GetMetadata struct {
 }
 
 type Metadata struct {
-	Name       string `json:"name" yaml:"name"`
-	Chart      string `json:"chart" yaml:"chart"`
-	Version    string `json:"version" yaml:"version"`
-	AppVersion string `json:"appVersion" yaml:"appVersion"`
-	Namespace  string `json:"namespace" yaml:"namespace"`
-	Revision   int    `json:"revision" yaml:"revision"`
-	Status     string `json:"status" yaml:"status"`
-	DeployedAt string `json:"deployedAt" yaml:"deployedAt"`
+	Name         string              `json:"name" yaml:"name"`
+	Chart        string              `json:"chart" yaml:"chart"`
+	Version      string              `json:"version" yaml:"version"`
+	AppVersion   string              `json:"appVersion" yaml:"appVersion"`
+	Annotations  map[string]string   `json:"annotations,omitempty" yaml:"annotations,omitempty"`
+	Dependencies []*chart.Dependency `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
+	Namespace    string              `json:"namespace" yaml:"namespace"`
+	Revision     int                 `json:"revision" yaml:"revision"`
+	Status       string              `json:"status" yaml:"status"`
+	DeployedAt   string              `json:"deployedAt" yaml:"deployedAt"`
 }
 
 // NewGetMetadata creates a new GetMetadata object with the given configuration.
@@ -57,13 +65,26 @@ func (g *GetMetadata) Run(name string) (*Metadata, error) {
 	}
 
 	return &Metadata{
-		Name:       rel.Name,
-		Chart:      rel.Chart.Metadata.Name,
-		Version:    rel.Chart.Metadata.Version,
-		AppVersion: rel.Chart.Metadata.AppVersion,
-		Namespace:  rel.Namespace,
-		Revision:   rel.Version,
-		Status:     rel.Info.Status.String(),
-		DeployedAt: rel.Info.LastDeployed.Format(time.RFC3339),
+		Name:         rel.Name,
+		Chart:        rel.Chart.Metadata.Name,
+		Version:      rel.Chart.Metadata.Version,
+		AppVersion:   rel.Chart.Metadata.AppVersion,
+		Dependencies: rel.Chart.Metadata.Dependencies,
+		Annotations:  rel.Chart.Metadata.Annotations,
+		Namespace:    rel.Namespace,
+		Revision:     rel.Version,
+		Status:       rel.Info.Status.String(),
+		DeployedAt:   rel.Info.LastDeployed.Format(time.RFC3339),
 	}, nil
 }
+
+// FormattedDepNames formats metadata.dependencies names into a comma-separated list.
+func (m *Metadata) FormattedDepNames() string {
+	depsNames := make([]string, 0, len(m.Dependencies))
+	for _, dep := range m.Dependencies {
+		depsNames = append(depsNames, dep.Name)
+	}
+	sort.StringSlice(depsNames).Sort()
+
+	return strings.Join(depsNames, ",")
+}
diff --git a/pkg/chart/dependency.go b/pkg/chart/dependency.go
index 4ef5eeb32..eda0f5a89 100644
--- a/pkg/chart/dependency.go
+++ b/pkg/chart/dependency.go
@@ -25,28 +25,28 @@ type Dependency struct {
 	// Name is the name of the dependency.
 	//
 	// This must mach the name in the dependency's Chart.yaml.
-	Name string `json:"name"`
+	Name string `json:"name" yaml:"name"`
 	// Version is the version (range) of this chart.
 	//
 	// A lock file will always produce a single version, while a dependency
 	// may contain a semantic version range.
-	Version string `json:"version,omitempty"`
+	Version string `json:"version,omitempty" yaml:"version,omitempty"`
 	// The URL to the repository.
 	//
 	// Appending `index.yaml` to this string should result in a URL that can be
 	// used to fetch the repository index.
-	Repository string `json:"repository"`
+	Repository string `json:"repository" yaml:"repository"`
 	// A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
-	Condition string `json:"condition,omitempty"`
+	Condition string `json:"condition,omitempty" yaml:"condition,omitempty"`
 	// Tags can be used to group charts for enabling/disabling together
-	Tags []string `json:"tags,omitempty"`
+	Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
 	// Enabled bool determines if chart should be loaded
-	Enabled bool `json:"enabled,omitempty"`
+	Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
 	// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
 	// string or pair of child/parent sublist items.
-	ImportValues []interface{} `json:"import-values,omitempty"`
+	ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"`
 	// Alias usable alias to be used for the chart
-	Alias string `json:"alias,omitempty"`
+	Alias string `json:"alias,omitempty" yaml:"alias,omitempty"`
 }
 
 // Validate checks for common problems with the dependency datastructure in
diff --git a/pkg/release/mock.go b/pkg/release/mock.go
index a28e1dc16..eb0b5157d 100644
--- a/pkg/release/mock.go
+++ b/pkg/release/mock.go
@@ -74,6 +74,24 @@ func Mock(opts *MockReleaseOptions) *Release {
 				Name:       "foo",
 				Version:    "0.1.0-beta.1",
 				AppVersion: "1.0",
+				Annotations: map[string]string{
+					"category":  "web-apps",
+					"supported": "true",
+				},
+				Dependencies: []*chart.Dependency{
+					{
+						Name:       "cool-plugin",
+						Version:    "1.0.0",
+						Repository: "https://coolplugin.io/charts",
+						Condition:  "coolPlugin.enabled",
+						Enabled:    true,
+					},
+					{
+						Name:      "crds",
+						Version:   "2.7.1",
+						Condition: "crds.enabled",
+					},
+				},
 			},
 			Templates: []*chart.File{
 				{Name: "templates/foo.tpl", Data: []byte(MockManifest)},


Test patch:
diff --git a/cmd/helm/testdata/output/get-metadata.json b/cmd/helm/testdata/output/get-metadata.json
index 1d5152b24..4c015b977 100644
--- a/cmd/helm/testdata/output/get-metadata.json
+++ b/cmd/helm/testdata/output/get-metadata.json
@@ -1,1 +1,1 @@
-{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","namespace":"default","revision":1,"status":"deployed","deployedAt":"1977-09-02T22:04:05Z"}
+{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","annotations":{"category":"web-apps","supported":"true"},"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"}
diff --git a/cmd/helm/testdata/output/get-metadata.txt b/cmd/helm/testdata/output/get-metadata.txt
index b91f1b86a..01083b333 100644
--- a/cmd/helm/testdata/output/get-metadata.txt
+++ b/cmd/helm/testdata/output/get-metadata.txt
@@ -2,6 +2,8 @@ NAME: thomas-guide
 CHART: foo
 VERSION: 0.1.0-beta.1
 APP_VERSION: 1.0
+ANNOTATIONS: category=web-apps,supported=true
+DEPENDENCIES: cool-plugin,crds
 NAMESPACE: default
 REVISION: 1
 STATUS: deployed
diff --git a/cmd/helm/testdata/output/get-metadata.yaml b/cmd/helm/testdata/output/get-metadata.yaml
index b6d49b038..6298436c9 100644
--- a/cmd/helm/testdata/output/get-metadata.yaml
+++ b/cmd/helm/testdata/output/get-metadata.yaml
@@ -1,5 +1,18 @@
+annotations:
+  category: web-apps
+  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"
 name: thomas-guide
 namespace: default