Skip to content

Commit

Permalink
feat(helm): Supporting helm3 to show up resource names that were depl…
Browse files Browse the repository at this point in the history
…oyed as part of release in helm status command

Creating a new PR based on this existing stale PR #7728

Signed-off-by: Soujanya Mangipudi <somangip@microsoft.com>

# Conflicts:
#	go.sum
  • Loading branch information
SoujanyaMangipudi committed Sep 9, 2022
1 parent bed2312 commit 9d5be80
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 6 deletions.
4 changes: 4 additions & 0 deletions cmd/helm/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
}

if len(s.release.Info.Resources) > 0 {
fmt.Fprintf(out, "RESOURCES:\n%s\n", s.release.Info.Resources)
}

executions := executionsByHookEvent(s.release)
if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 {
fmt.Fprintln(out, "TEST SUITE: None")
Expand Down
20 changes: 20 additions & 0 deletions cmd/helm/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ func TestStatusCmd(t *testing.T) {
Status: release.StatusDeployed,
Notes: "release notes",
}),
}, {
name: "get status of a deployed release with resources",
cmd: "status flummoxed-chickadee",
golden: "output/status-with-resources.txt",
rels: releasesMockWithStatus(
&release.Info{
Resources: "hello resource",
Status: release.StatusDeployed,
},
),
}, {
name: "get status of a deployed release with resources in json",
cmd: "status flummoxed-chickadee -o json",
golden: "output/status-with-resources.json",
rels: releasesMockWithStatus(
&release.Info{
Resources: "hello resource",
Status: release.StatusDeployed,
},
),
}, {
name: "get status of a deployed release with test suite",
cmd: "status flummoxed-chickadee",
Expand Down
1 change: 1 addition & 0 deletions cmd/helm/testdata/output/status-with-resources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"flummoxed-chickadee","info":{"first_deployed":"","last_deployed":"2016-01-16T00:00:00Z","deleted":"","status":"deployed","resources":"hello resource"},"namespace":"default"}
8 changes: 8 additions & 0 deletions cmd/helm/testdata/output/status-with-resources.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: Sat Jan 16 00:00:00 2016
NAMESPACE: default
STATUS: deployed
REVISION: 0
RESOURCES:
hello resource
TEST SUITE: None
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ require (
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
Expand Down
16 changes: 15 additions & 1 deletion pkg/action/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package action

import (
"bytes"

"helm.sh/helm/v3/pkg/release"
)

Expand Down Expand Up @@ -47,5 +49,17 @@ func (s *Status) Run(name string) (*release.Release, error) {
return nil, err
}

return s.cfg.releaseContent(name, s.Version)
rel, err := s.cfg.releaseContent(name, s.Version)
if err != nil {
return nil, err
}
resources, _ := s.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), false)
resp, err := s.cfg.KubeClient.Get(resources, bytes.NewBufferString(rel.Manifest))
if err != nil {
return nil, err
}
if resp != "" {
rel.Info.Resources = resp
}
return rel, nil
}
154 changes: 149 additions & 5 deletions pkg/kube/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ limitations under the License.
package kube // import "helm.sh/helm/v3/pkg/kube"

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"time"
Expand All @@ -38,7 +40,9 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
Expand All @@ -47,8 +51,10 @@ import (
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
cachetools "k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
"k8s.io/kubectl/pkg/cmd/get"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)

Expand Down Expand Up @@ -132,6 +138,134 @@ func (c *Client) Create(resources ResourceList) (*Result, error) {
return &Result{Created: resources}, nil
}

func transformRequests(req *rest.Request) {
tableParam := strings.Join([]string{
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName),
"application/json",
}, ",")
req.SetHeader("Accept", tableParam)

// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
req.Param("includeObject", "Object")
}

func (c *Client) Get(resources ResourceList, reader io.Reader) (string, error) {
buf := new(bytes.Buffer)
printFlags := get.NewHumanPrintFlags()
typePrinter, _ := printFlags.ToPrinter("")
printer := &get.TablePrinter{Delegate: typePrinter}
objs := make(map[string][]runtime.Object)

podSelectors := []map[string]string{}
err := resources.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}

gvk := info.ResourceMapping().GroupVersionKind
vk := gvk.Version + "/" + gvk.Kind
obj, err := getResource(info)
if err != nil {
fmt.Fprintf(buf, "Get resource %s failed, err:%v\n", info.Name, err)
} else {
objs[vk] = append(objs[vk], obj)

objs, err = c.getSelectRelationPod(info, objs, &podSelectors)
if err != nil {
c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
}
}

return nil
})
if err != nil {
return "", err
}

var keys []string
for key := range objs {
keys = append(keys, key)
}

for _, t := range keys {
if _, err = fmt.Fprintf(buf, "==> %s\n", t); err != nil {
return "", err
}
vk := objs[t]
for _, resource := range vk {
if err := printer.PrintObj(resource, buf); err != nil {
c.Log("failed to print object type %s: %v", t, err)
return "", err
}
}
if _, err := buf.WriteString("\n"); err != nil {
return "", err
}
}
return buf.String(), nil
}

func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]runtime.Object, podSelectors *[]map[string]string) (map[string][]runtime.Object, error) {
if info == nil {
return objs, nil
}
c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
selector, ok, _ := getSelectorFromObject(info.Object)
if !ok {
return objs, nil
}

for index := range *podSelectors {
if reflect.DeepEqual((*podSelectors)[index], selector) {
// check if pods for selectors are already added. This avoids duplicate printing of pods
return objs, nil
}
}

*podSelectors = append(*podSelectors, selector)

infos, err := c.Factory.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(info.Namespace).
DefaultNamespace().
ResourceTypes("pods").
LabelSelector(labels.Set(selector).AsSelector().String()).
TransformRequests(transformRequests).
Do().Infos()
if err != nil {
return objs, err
}
vk := "v1/Pod(related)"

for _, info := range infos {
objs[vk] = append(objs[vk], info.Object)
}
return objs, nil
}

func getSelectorFromObject(obj runtime.Object) (map[string]string, bool, error) {
typed := obj.(*unstructured.Unstructured)
kind := typed.Object["kind"]
switch kind {
case "ReplicaSet", "Deployment", "StatefulSet", "DaemonSet", "Job":
return unstructured.NestedStringMap(typed.Object, "spec", "selector", "matchLabels")
case "ReplicationController":
return unstructured.NestedStringMap(typed.Object, "spec", "selector")
default:
return nil, false, nil
}
}

func getResource(info *resource.Info) (runtime.Object, error) {
obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name)
if err != nil {
return nil, err
}
return obj, nil
}

// Wait waits up to the given timeout for the specified resources to be ready.
func (c *Client) Wait(resources ResourceList, timeout time.Duration) error {
cs, err := c.getKubeClient()
Expand Down Expand Up @@ -207,11 +341,21 @@ func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) {
if err != nil {
return nil, err
}
result, err := c.newBuilder().
Unstructured().
Schema(schema).
Stream(reader, "").
Do().Infos()
var result ResourceList
if validate {
result, err = c.newBuilder().
Unstructured().
Schema(schema).
Stream(reader, "").
Do().Infos()
} else {
result, err = c.newBuilder().
Unstructured().
Schema(schema).
Stream(reader, "").
TransformRequests(transformRequests).
Do().Infos()
}
return result, scrubValidationError(err)
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/kube/fake/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ func (p *PrintingKubeClient) Create(resources kube.ResourceList) (*kube.Result,
return &kube.Result{Created: resources}, nil
}

func (p *PrintingKubeClient) Get(resources kube.ResourceList, reader io.Reader) (string, error) {
_, err := io.Copy(p.Out, bufferize(resources))
if err != nil {
return "", err
}
return "", nil
}

func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error {
_, err := io.Copy(p.Out, bufferize(resources))
return err
Expand Down
2 changes: 2 additions & 0 deletions pkg/kube/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type Interface interface {

// IsReachable checks whether the client is able to connect to the cluster.
IsReachable() error

Get(resources ResourceList, reader io.Reader) (string, error)
}

// InterfaceExt is introduced to avoid breaking backwards compatibility for Interface implementers.
Expand Down
2 changes: 2 additions & 0 deletions pkg/release/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ type Info struct {
Status Status `json:"status,omitempty"`
// Contains the rendered templates/NOTES.txt if available
Notes string `json:"notes,omitempty"`
// Contains the deployed resources information
Resources string `json:"resources,omitempty"`
}

0 comments on commit 9d5be80

Please sign in to comment.