Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [ISSUE-1401]: Added helm get values option in Helm collector #1402

Merged
merged 4 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/crds/troubleshoot.sh_collectors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ spec:
type: object
helm:
properties:
collectValues:
type: boolean
collectorName:
type: string
exclude:
Expand Down
2 changes: 2 additions & 0 deletions config/crds/troubleshoot.sh_preflights.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,8 @@ spec:
type: object
helm:
properties:
collectValues:
type: boolean
collectorName:
type: string
exclude:
Expand Down
2 changes: 2 additions & 0 deletions config/crds/troubleshoot.sh_supportbundles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1941,6 +1941,8 @@ spec:
type: object
helm:
properties:
collectValues:
type: boolean
collectorName:
type: string
exclude:
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/troubleshoot/v1beta2/collector_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ type Helm struct {
CollectorMeta `json:",inline" yaml:",inline"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
ReleaseName string `json:"releaseName,omitempty" yaml:"releaseName,omitempty"`
CollectValues bool `json:"collectValues,omitempty" yaml:"collectValues,omitempty"`
}

type Goldpinger struct {
Expand Down
91 changes: 68 additions & 23 deletions pkg/collect/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"

"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"

"helm.sh/helm/v3/pkg/action"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -39,10 +38,11 @@ type ReleaseInfo struct {

// Helm release version information struct
type VersionInfo struct {
Revision string `json:"revision"`
Date string `json:"date"`
Status string `json:"status"`
IsPending bool `json:"isPending,omitempty"`
Revision string `json:"revision"`
Date string `json:"date"`
Status string `json:"status"`
IsPending bool `json:"isPending,omitempty"`
Values map[string]interface{} `json:"values,omitempty"`
}

func (c *CollectHelm) Title() string {
Expand All @@ -57,9 +57,15 @@ func (c *CollectHelm) Collect(progressChan chan<- interface{}) (CollectorResult,

output := NewResult()

releaseInfos, err := helmReleaseHistoryCollector(c.Collector.ReleaseName, c.Collector.Namespace)
releaseInfos, err := helmReleaseHistoryCollector(c.Collector.ReleaseName, c.Collector.Namespace, c.Collector.CollectValues)
if err != nil {
return nil, errors.Wrap(err, "failed to get Helm release history")
errsToMarhsal := []string{}
for _, e := range err {
errsToMarhsal = append(errsToMarhsal, e.Error())
}
output.SaveResult(c.BundlePath, "helm/errors.json", marshalErrors(errsToMarhsal))
klog.Errorf("error collecting helm release info: %v", err)
return output, nil
}

releaseInfoByNamespace := helmReleaseInfoByNamespaces(releaseInfos)
Expand All @@ -72,40 +78,42 @@ func (c *CollectHelm) Collect(progressChan chan<- interface{}) (CollectorResult,
}

filePath := fmt.Sprintf("helm/%s.json", namespace)

err := output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(helmHistoryJson))
if err != nil {
return nil, err
if c.Collector.ReleaseName != "" {
filePath = fmt.Sprintf("helm/%s/%s.json", namespace, c.Collector.ReleaseName)
}

output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(helmHistoryJson))
}

return output, nil
}

func helmReleaseHistoryCollector(releaseName string, namespace string) ([]ReleaseInfo, error) {
func helmReleaseHistoryCollector(releaseName string, namespace string, collectValues bool) ([]ReleaseInfo, []error) {
var results []ReleaseInfo
error_list := []error{}

actionConfig := new(action.Configuration)
if err := actionConfig.Init(nil, namespace, "", klog.V(2).Infof); err != nil {
return nil, errors.Wrap(err, "failed to initialize Helm action config")
return nil, []error{err}
}

// If releaseName is specified, get the history of that release
if releaseName != "" {
getAction := action.NewGet(actionConfig)
r, err := getAction.Run(releaseName)
if err != nil {
return nil, errors.Wrapf(err, "failed to get Helm release %s", releaseName)
return nil, []error{err}
}
versionInfo, err := getVersionInfo(actionConfig, r.Name, r.Namespace, collectValues)
results = append(results, ReleaseInfo{
ReleaseName: r.Name,
Chart: r.Chart.Metadata.Name,
ChartVersion: r.Chart.Metadata.Version,
AppVersion: r.Chart.Metadata.AppVersion,
Namespace: r.Namespace,
VersionInfo: getVersionInfo(actionConfig, releaseName),
VersionInfo: versionInfo,
})
return results, nil
return results, []error{err}
}

// If releaseName is not specified, get the history of all releases
Expand All @@ -114,39 +122,64 @@ func helmReleaseHistoryCollector(releaseName string, namespace string) ([]Releas
listAction := action.NewList(actionConfig)
releases, err := listAction.Run()
if err != nil {
log.Fatalf("Failed to list Helm releases: %v", err)
return nil, []error{err}
}

for _, r := range releases {
versionInfo, err := getVersionInfo(actionConfig, r.Name, r.Namespace, collectValues)
if err != nil {
error_list = append(error_list, err)
}
results = append(results, ReleaseInfo{
ReleaseName: r.Name,
Chart: r.Chart.Metadata.Name,
ChartVersion: r.Chart.Metadata.Version,
AppVersion: r.Chart.Metadata.AppVersion,
Namespace: r.Namespace,
VersionInfo: getVersionInfo(actionConfig, r.Name),
VersionInfo: versionInfo,
})
}

if len(error_list) > 0 {
return nil, error_list
}
return results, nil
}

func getVersionInfo(actionConfig *action.Configuration, releaseName string) []VersionInfo {
func getVersionInfo(actionConfig *action.Configuration, releaseName, namespace string, collectValues bool) ([]VersionInfo, error) {

versionCollect := []VersionInfo{}
error_list := []error{}

history, _ := action.NewHistory(actionConfig).Run(releaseName)
history, err := action.NewHistory(actionConfig).Run(releaseName)
if err != nil {
return nil, err
}

for _, release := range history {
values := map[string]interface{}{}
if collectValues {
values, err = getHelmValues(releaseName, namespace, release.Version)
if err != nil {
error_list = append(error_list, err)
}
}

versionCollect = append(versionCollect, VersionInfo{
Revision: strconv.Itoa(release.Version),
Date: release.Info.LastDeployed.String(),
Status: release.Info.Status.String(),
IsPending: release.Info.Status.IsPending(),
Values: values,
})
}
return versionCollect
if len(error_list) > 0 {
errs := []string{}
for _, e := range error_list {
errs = append(errs, e.Error())
}
return nil, errors.New(strings.Join(errs, "\n"))
}
return versionCollect, nil
}

func helmReleaseInfoByNamespaces(releaseInfo []ReleaseInfo) map[string][]ReleaseInfo {
Expand All @@ -158,3 +191,15 @@ func helmReleaseInfoByNamespaces(releaseInfo []ReleaseInfo) map[string][]Release

return releaseInfoByNamespace
}

func getHelmValues(releaseName, namespace string, revision int) (map[string]interface{}, error) {
actionConfig := new(action.Configuration)
if err := actionConfig.Init(nil, namespace, "", klog.V(2).Infof); err != nil {
return nil, err
}
getAction := action.NewGetValues(actionConfig)
getAction.AllValues = true
getAction.Version = revision
avaakash marked this conversation as resolved.
Show resolved Hide resolved
helmValues, err := getAction.Run(releaseName)
return helmValues, err
}
3 changes: 3 additions & 0 deletions schemas/collector-troubleshoot-v1beta2.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@
"helm": {
"type": "object",
"properties": {
"collectValues": {
"type": "boolean"
},
"collectorName": {
"type": "string"
},
Expand Down
3 changes: 3 additions & 0 deletions schemas/preflight-troubleshoot-v1beta2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2888,6 +2888,9 @@
"helm": {
"type": "object",
"properties": {
"collectValues": {
"type": "boolean"
},
"collectorName": {
"type": "string"
},
Expand Down
3 changes: 3 additions & 0 deletions schemas/supportbundle-troubleshoot-v1beta2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2934,6 +2934,9 @@
"helm": {
"type": "object",
"properties": {
"collectValues": {
"type": "boolean"
},
"collectorName": {
"type": "string"
},
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/support-bundle/helm_collector_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Test_HelmCollector(t *testing.T) {
Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
cluster := getClusterFromContext(t, ctx, ClusterName)
manager := helm.New(cluster.GetKubeconfig())
manager.RunInstall(helm.WithName(releaseName), helm.WithNamespace(c.Namespace()), helm.WithChart(filepath.Join(curDir, "testdata/charts/nginx-15.2.0.tgz")), helm.WithWait(), helm.WithTimeout("1m"))
manager.RunInstall(helm.WithName(releaseName), helm.WithNamespace(c.Namespace()), helm.WithChart(filepath.Join(curDir, "testdata/charts/nginx-15.2.0.tgz")), helm.WithArgs("-f "+filepath.Join(curDir, "testdata/helm-values.yaml")), helm.WithWait(), helm.WithTimeout("1m"))
//ignore error to allow test to speed up, helm collector will catch the pending or deployed helm release status
return ctx
}).
Expand Down Expand Up @@ -62,10 +62,11 @@ func Test_HelmCollector(t *testing.T) {
if err != nil {
t.Fatal(err)
}

assert.Equal(t, 1, len(results))
assert.Equal(t, releaseName, results[0].ReleaseName)
assert.Equal(t, "nginx", results[0].Chart)
assert.Equal(t, map[string]interface{}{"name": "TEST_ENV_VAR", "value": "test-value"}, results[0].VersionInfo[0].Values["extraEnvVars"].([]interface{})[0])
assert.Equal(t, "1.25.2-debian-11-r3", results[0].VersionInfo[0].Values["image"].(map[string]interface{})["tag"])
return ctx
}).
Teardown(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
Expand Down
4 changes: 3 additions & 1 deletion test/e2e/support-bundle/spec/helm.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: default
spec:
collectors:
- helm: {}
- helm:
collectValues: true
4 changes: 4 additions & 0 deletions test/e2e/support-bundle/testdata/helm-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
extraEnvVars:
- name: TEST_ENV_VAR
value: "test-value"
Loading