Skip to content

Commit

Permalink
adds helm information gather
Browse files Browse the repository at this point in the history
* feat: gather helm information

* feat(gather/hemlchart_info): add resource counter

* docs: adds helmchart_info sample data

* test: adds some unit test for helmchart_gather

* test: expanding gather_helm_info tests

* feat: adding helmchart gatherer

* docs: update gathered-data

* Revert "feat: adding helmchart gatherer"

This reverts commit e57a5c1.

* refactor: revert helmcharts gatherer

* style: fix lint errors

* refactor(helm_info): remove if for label checking

* refactor(gather_info): unexpose labelChartNameKey const

* fix: wrong log message

* test(helm_gather_info): invalid resources
  • Loading branch information
rluders committed Dec 20, 2023
1 parent ef30d15 commit 7435ebe
Show file tree
Hide file tree
Showing 10 changed files with 678 additions and 4 deletions.
24 changes: 24 additions & 0 deletions docs/gathered-data.md
Expand Up @@ -404,6 +404,30 @@ filtered to only include those with a deployment_validation_operator_ prefix.
- 4.10


## HelmInfo

Collects summarized info about the helm usage on a cluster
in a generic fashion

### API Reference
None

### Sample data
- [docs/insights-archive-sample/config/helmchart_info.json](./insights-archive-sample/config/helmchart_info.json)

### Location in archive
- `config/helmchart_info.json`

### Config ID
`workloads/helmchart_info`

### Released version
- 4.15.0

### Backported versions
None


## HostSubnet

collects HostSubnet information
Expand Down
19 changes: 19 additions & 0 deletions docs/insights-archive-sample/config/helmchart_info.json
@@ -0,0 +1,19 @@
{
"6c7e03817b27fbe6cc67ae835381df521b8d847dd029fb2df483f1a327b63582": [
{ "name": "jenkins", "version": "0.0.3", "resources": { "services": 2 } }
],
"a085ddf97d8c556fd4f965392f7d3446c4b11df0544848878e06b20c96523064": [
{
"name": "nodejs",
"version": "",
"resources": { "deployments": 1, "replicasets": 1, "services": 1 }
}
],
"e976fcc461dc1b1ad177c083135393e90ded18b3707987d4b6adf78e86e687ed": [
{
"name": "nodejs",
"version": "",
"resources": { "deployments": 1, "replicasets": 1, "services": 1 }
}
]
}
2 changes: 1 addition & 1 deletion pkg/gather/gather.go
Expand Up @@ -65,7 +65,7 @@ func CreateAllGatherers(
gatherKubeConfig, gatherProtoKubeConfig, metricsGatherKubeConfig, alertsGatherKubeConfig,
anonymizer, configObserver,
)
workloadsGatherer := workloads.New(gatherProtoKubeConfig)
workloadsGatherer := workloads.New(gatherKubeConfig, gatherProtoKubeConfig)
conditionalGatherer := conditional.New(
gatherProtoKubeConfig, metricsGatherKubeConfig, gatherKubeConfig, configObserver, insightsClient,
)
Expand Down
167 changes: 167 additions & 0 deletions pkg/gatherers/workloads/gather_helm_info.go
@@ -0,0 +1,167 @@
package workloads

import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"

"k8s.io/apimachinery/pkg/runtime/schema"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/dynamic"
"k8s.io/klog/v2"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/openshift/insights-operator/pkg/record"
)

const labelChartNameKey = "helm.sh/chart"

// GatherHelmInfo Collects summarized info about the helm usage on a cluster
// in a generic fashion
//
// ### API Reference
// None
//
// ### Sample data
// - docs/insights-archive-sample/config/helmchart_info.json
//
// ### Location in archive
// - `config/helmchart_info.json`
//
// ### Config ID
// `workloads/helmchart_info`
//
// ### Released version
// - 4.15.0
//
// ### Backported versions
// None
func (g *Gatherer) GatherHelmInfo(ctx context.Context) ([]record.Record, []error) {
dynamicClient, err := dynamic.NewForConfig(g.gatherKubeConfig)
if err != nil {
return nil, []error{err}
}

return gatherHelmInfo(ctx, dynamicClient)
}

func gatherHelmInfo(
ctx context.Context,
dynamicClient dynamic.Interface,
) ([]record.Record, []error) {
resources := []schema.GroupVersionResource{
{Group: "apps", Version: "v1", Resource: "replicasets"},
{Group: "apps", Version: "v1", Resource: "daemonsets"},
{Group: "apps", Version: "v1", Resource: "statefulsets"},
{Group: "", Version: "v1", Resource: "services"},
{Group: "apps", Version: "v1", Resource: "deployments"},
}

var errs []error
var records []record.Record
helmList := newHelmChartInfoList()

for _, resource := range resources {
listOptions := metav1.ListOptions{LabelSelector: "app.kubernetes.io/managed-by=Helm"}

items, err := dynamicClient.Resource(resource).List(ctx, listOptions)
if errors.IsNotFound(err) {
return nil, nil
}
if err != nil {
klog.V(2).Infof("Unable to list %s resource due to: %s", resource, err)
errs = append(errs, err)
continue
}

for _, item := range items.Items {
labels := item.GetLabels()

// Anonymize the namespace to make it unique identifier
hash, err := createHash(item.GetNamespace())
if err != nil {
klog.Errorf("unable to hash the namespace '%s': %v", labels[labelChartNameKey], err)
continue
}

name, version := helmChartNameAndVersion(labels[labelChartNameKey])
if name == "" && version == "" {
// some helm-maneged resource may not have reference to the chart
klog.Infof("unable to get HelmChart from %s on %s from %s.", resource.Resource, item.GetNamespace(), item.GetName())
continue
}

helmList.addItem(hash, resource.Resource, HelmChartInfo{
Name: name,
Version: version,
})
}
}

if len(helmList.Namespaces) > 0 {
records = []record.Record{
{
Name: "config/helmchart_info",
Item: record.JSONMarshaller{Object: &helmList.Namespaces},
},
}
}

if len(errs) > 0 {
return records, errs
}

return records, nil
}

func createHash(chartName string) (string, error) {
h := sha256.New()
_, err := h.Write([]byte(chartName))
if err != nil {
return "", err
}

hashInBytes := h.Sum(nil)
hash := hex.EncodeToString(hashInBytes)

return hash, nil
}

func helmChartNameAndVersion(chart string) (name, version string) {
parts := strings.Split(chart, "-")

// no version found
if len(parts) == 1 {
return chart, ""
}

name = strings.Join(parts[:len(parts)-1], "-")

// best guess to get the version
version = parts[len(parts)-1]
// check for standard version format
if !strings.Contains(version, ".") {
// maybe it is a string version
if !isStringVersion(version) {
// not a valid version, add to name and version should be empty
name = fmt.Sprintf("%s-%s", name, version)
version = ""
}
}

return name, version
}

func isStringVersion(version string) bool {
stringVersions := []string{"latest", "beta", "alpha"}
for _, v := range stringVersions {
if v == version {
return true
}
}
return false
}

0 comments on commit 7435ebe

Please sign in to comment.