mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
feat(helm): Supporting helm3 to show up resource names that were deployed as part of release in helm status command
Creating a new PR based on this existing stale PR https://github.com/helm/helm/pull/7728 Signed-off-by: Soujanya Mangipudi <somangip@microsoft.com> # Conflicts: # go.sum
This commit is contained in:
parent
bed23120b0
commit
9d5be803bc
11 changed files with 211 additions and 6 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
1
cmd/helm/testdata/output/status-with-resources.json
vendored
Normal file
1
cmd/helm/testdata/output/status-with-resources.json
vendored
Normal file
|
|
@ -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
cmd/helm/testdata/output/status-with-resources.txt
vendored
Normal file
8
cmd/helm/testdata/output/status-with-resources.txt
vendored
Normal file
|
|
@ -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
go.mod
1
go.mod
|
|
@ -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
|
||||
|
|
|
|||
1
go.sum
1
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue