Merge pull request #10912 from SoujanyaMangipudi/main

feat(helm): Supporting helm3 to show up resource names in helm status
pull/11624/head
Matt Farina 2 years ago committed by GitHub
commit eb7150fc61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -59,7 +59,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return tpl(template, data, out)
}
return output.Table.Write(out, &statusPrinter{res, true, false})
return output.Table.Write(out, &statusPrinter{res, true, false, false})
},
}

@ -141,7 +141,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return errors.Wrap(err, "INSTALLATION FAILED")
}
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
},
}

@ -72,7 +72,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
return runErr
}
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}); err != nil {
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}); err != nil {
return err
}

@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"bytes"
"fmt"
"io"
"log"
@ -25,6 +26,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/cmd/get"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
@ -41,7 +44,7 @@ The status consists of:
- state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback)
- revision of the release
- description of the release (can be completion message or error message, need to enable --show-desc)
- list of resources that this release consists of, sorted by kind
- list of resources that this release consists of (need to enable --show-resources)
- details on last test suite run, if applicable
- additional notes provided by the chart
`
@ -70,7 +73,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// strip chart metadata from the output
rel.Chart = nil
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription})
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources})
},
}
@ -92,6 +95,8 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
bindOutputFlag(cmd, &outfmt)
f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release")
f.BoolVar(&client.ShowResources, "show-resources", false, "if set, display the resources of the named release")
return cmd
}
@ -99,6 +104,7 @@ type statusPrinter struct {
release *release.Release
debug bool
showDescription bool
showResources bool
}
func (s statusPrinter) WriteJSON(out io.Writer) error {
@ -124,6 +130,33 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
}
if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 {
buf := new(bytes.Buffer)
printFlags := get.NewHumanPrintFlags()
typePrinter, _ := printFlags.ToPrinter("")
printer := &get.TablePrinter{Delegate: typePrinter}
var keys []string
for key := range s.release.Info.Resources {
keys = append(keys, key)
}
for _, t := range keys {
fmt.Fprintf(buf, "==> %s\n", t)
vk := s.release.Info.Resources[t]
for _, resource := range vk {
if err := printer.PrintObj(resource, buf); err != nil {
fmt.Fprintf(buf, "failed to print object type %s: %v\n", t, err)
}
}
buf.WriteString("\n")
}
fmt.Fprintf(out, "RESOURCES:\n%s\n", buf.String())
}
executions := executionsByHookEvent(s.release)
if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 {
fmt.Fprintln(out, "TEST SUITE: None")

@ -68,6 +68,24 @@ func TestStatusCmd(t *testing.T) {
Status: release.StatusDeployed,
Notes: "release notes",
}),
}, {
name: "get status of a deployed release with resources",
cmd: "status --show-resources flummoxed-chickadee",
golden: "output/status-with-resources.txt",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
},
),
}, {
name: "get status of a deployed release with resources in json",
cmd: "status --show-resources flummoxed-chickadee -o json",
golden: "output/status-with-resources.json",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
},
),
}, {
name: "get status of a deployed release with test suite",
cmd: "status flummoxed-chickadee",

@ -0,0 +1 @@
{"name":"flummoxed-chickadee","info":{"first_deployed":"","last_deployed":"2016-01-16T00:00:00Z","deleted":"","status":"deployed"},"namespace":"default"}

@ -0,0 +1,6 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: Sat Jan 16 00:00:00 2016
NAMESPACE: default
STATUS: deployed
REVISION: 0
TEST SUITE: None

@ -123,7 +123,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if err != nil {
return err
}
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
} else if err != nil {
return err
}
@ -205,7 +205,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
}
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
},
}

@ -1,6 +1,6 @@
module helm.sh/helm/v3
go 1.18
go 1.17
require (
github.com/BurntSushi/toml v1.2.0
@ -29,7 +29,7 @@ require (
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.7.4
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.3.0
golang.org/x/term v0.2.0
@ -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

739
go.sum

File diff suppressed because it is too large Load Diff

@ -17,6 +17,9 @@ limitations under the License.
package action
import (
"bytes"
"helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/release"
)
@ -32,6 +35,10 @@ type Status struct {
// only affect print type table.
// TODO Helm 4: Remove this flag and output the description by default.
ShowDescription bool
// If true, display resources of release to output format
// TODO Helm 4: Remove this flag and output the resources by default.
ShowResources bool
}
// NewStatus creates a new Status object with the given configuration.
@ -47,5 +54,26 @@ func (s *Status) Run(name string) (*release.Release, error) {
return nil, err
}
return s.cfg.releaseContent(name, s.Version)
if !s.ShowResources {
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)
if kubeClient, ok := s.cfg.KubeClient.(kube.InterfaceResources); ok {
resp, err := kubeClient.Get(resources, bytes.NewBufferString(rel.Manifest))
if err != nil {
return nil, err
}
rel.Info.Resources = resp
return rel, nil
}
return nil, err
}

@ -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,6 +51,7 @@ 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"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
@ -132,6 +137,111 @@ 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) (map[string][]runtime.Object, error) {
buf := new(bytes.Buffer)
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 nil, err
}
return objs, 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 +317,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)
}

@ -22,6 +22,7 @@ import (
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v3/pkg/kube"
@ -47,6 +48,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) (map[string][]runtime.Object, error) {
_, err := io.Copy(p.Out, bufferize(resources))
if err != nil {
return nil, err
}
return make(map[string][]runtime.Object), nil
}
func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error {
_, err := io.Copy(p.Out, bufferize(resources))
return err

@ -21,6 +21,7 @@ import (
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// Interface represents a client capable of communicating with the Kubernetes API.
@ -78,5 +79,14 @@ type InterfaceExt interface {
WaitForDelete(resources ResourceList, timeout time.Duration) error
}
// InterfaceResources is introduced to avoid breaking backwards compatibility for Interface implementers.
//
// TODO Helm 4: Remove InterfaceResources and integrate its method(s) into the Interface.
type InterfaceResources interface {
// Get details of deployed resources in ResourceList to be printed.
Get(resources ResourceList, reader io.Reader) (map[string][]runtime.Object, error)
}
var _ Interface = (*Client)(nil)
var _ InterfaceExt = (*Client)(nil)
var _ InterfaceResources = (*Client)(nil)

@ -16,6 +16,8 @@ limitations under the License.
package release
import (
"k8s.io/apimachinery/pkg/runtime"
"helm.sh/helm/v3/pkg/time"
)
@ -33,4 +35,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 map[string][]runtime.Object `json:"resources,omitempty"`
}

Loading…
Cancel
Save