adding value, yaml and describe views to helm-history (#2341)

* correct describe of helm history

* value, yaml and describe view for helm-history

* add value_extender and clean up code

* move showValues to the new extender
mine
Jayson Wang 2023-12-20 06:21:12 +08:00 committed by GitHub
parent ca72490350
commit 7bd7ec766a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 179 additions and 71 deletions

View File

@ -21,6 +21,7 @@ var (
_ Accessor = (*HelmChart)(nil)
_ Nuker = (*HelmChart)(nil)
_ Describer = (*HelmChart)(nil)
_ Valuer = (*HelmChart)(nil)
)
// HelmChart represents a helm chart.

View File

@ -7,19 +7,23 @@ import (
"context"
"fmt"
"strconv"
"strings"
"gopkg.in/yaml.v2"
"helm.sh/helm/v3/pkg/action"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render/helm"
"helm.sh/helm/v3/pkg/action"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*HelmHistory)(nil)
_ Nuker = (*HelmHistory)(nil)
_ Describer = (*HelmHistory)(nil)
_ Valuer = (*HelmHistory)(nil)
)
// HelmHistory represents a helm chart.
@ -55,12 +59,24 @@ func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, err
// Get returns a resource.
func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) {
ns, n := client.Namespaced(path)
fqn, rev, found := strings.Cut(path, ":")
if !found || len(rev) == 0 {
return nil, fmt.Errorf("invalid path %q", path)
}
ns, n := client.Namespaced(fqn)
cfg, err := ensureHelmConfig(h.Client(), ns)
if err != nil {
return nil, err
}
resp, err := action.NewGet(cfg).Run(n)
getter := action.NewGet(cfg)
getter.Version, err = strconv.Atoi(rev)
if err != nil {
return nil, err
}
resp, err := getter.Run(n)
if err != nil {
return nil, err
}
@ -70,32 +86,50 @@ func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error
// Describe returns the chart notes.
func (h *HelmHistory) Describe(path string) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client(), ns)
if err != nil {
return "", err
}
resp, err := action.NewGet(cfg).Run(n)
rel, err := h.Get(context.Background(), path)
if err != nil {
return "", err
}
return resp.Info.Notes, nil
resp, ok := rel.(helm.ReleaseRes)
if !ok {
return "", fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
}
return resp.Release.Info.Notes, nil
}
// ToYAML returns the chart manifest.
func (h *HelmHistory) ToYAML(path string, showManaged bool) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := ensureHelmConfig(h.Client(), ns)
if err != nil {
return "", err
}
resp, err := action.NewGet(cfg).Run(n)
rel, err := h.Get(context.Background(), path)
if err != nil {
return "", err
}
return resp.Manifest, nil
resp, ok := rel.(helm.ReleaseRes)
if !ok {
return "", fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
}
return resp.Release.Manifest, nil
}
// GetValues return the config for this chart.
func (h *HelmHistory) GetValues(path string, allValues bool) ([]byte, error) {
rel, err := h.Get(context.Background(), path)
if err != nil {
return nil, err
}
resp, ok := rel.(helm.ReleaseRes)
if !ok {
return nil, fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
}
if allValues {
return yaml.Marshal(resp.Release.Chart.Values)
}
return yaml.Marshal(resp.Release.Config)
}
func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error {

View File

@ -102,6 +102,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
client.NewGVR("v1/namespaces"): &Namespace{},
client.NewGVR("popeye"): &Popeye{},
client.NewGVR("helm"): &HelmChart{},
client.NewGVR("helm-history"): &HelmHistory{},
client.NewGVR("dir"): &Dir{},
}
@ -314,7 +315,7 @@ func loadHelm(m ResourceMetas) {
Kind: "History",
Namespaced: true,
Verbs: []string{"delete"},
Categories: []string{k9sCat},
Categories: []string{helmCat},
}
}

View File

@ -170,3 +170,9 @@ type Sanitizer interface {
// Sanitize nukes all resources in unhappy state.
Sanitize(context.Context, string) (int, error)
}
// Valuer represents a resource with values.
type Valuer interface {
// GetValues returns values for a resource.
GetValues(path string, allValues bool) ([]byte, error)
}

View File

@ -5,6 +5,7 @@ package model
import (
"context"
"fmt"
"strings"
"sync/atomic"
"time"
@ -18,6 +19,7 @@ import (
// Values tracks Helm values representations.
type Values struct {
factory dao.Factory
gvr client.GVR
inUpdate int32
path string
@ -34,20 +36,36 @@ func NewValues(gvr client.GVR, path string) *Values {
gvr: gvr,
path: path,
allValues: false,
lines: getValues(path, false),
}
}
func getHelmDao() *dao.HelmChart {
return Registry["helm"].DAO.(*dao.HelmChart)
// Init initializes the model.
func (v *Values) Init(f dao.Factory) error {
v.factory = f
var err error
v.lines, err = v.getValues()
return err
}
func getValues(path string, allValues bool) []string {
vals, err := getHelmDao().GetValues(path, allValues)
func (v *Values) getValues() ([]string, error) {
accessor, err := dao.AccessorFor(v.factory, v.gvr)
if err != nil {
log.Error().Err(err).Msgf("Failed to get Helm values")
return nil, err
}
return strings.Split(string(vals), "\n")
valuer, ok := accessor.(dao.Valuer)
if !ok {
return nil, fmt.Errorf("Resource %s is not Valuer", v.gvr)
}
values, err := valuer.GetValues(v.path, v.allValues)
if err != nil {
return nil, err
}
return strings.Split(string(values), "\n"), nil
}
// GVR returns the resource gvr.
@ -56,10 +74,16 @@ func (v *Values) GVR() client.GVR {
}
// ToggleValues toggles between user supplied values and computed values.
func (v *Values) ToggleValues() {
func (v *Values) ToggleValues() error {
v.allValues = !v.allValues
lines := getValues(v.path, v.allValues)
lines, err := v.getValues()
if err != nil {
return err
}
v.lines = lines
return nil
}
// GetPath returns the active resource path.

View File

@ -8,23 +8,19 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2"
"github.com/rs/zerolog/log"
)
// HelmChart represents a helm chart view.
type HelmChart struct {
ResourceViewer
Values *model.Values
}
// NewHelm returns a new alias view.
// NewHelmChart returns a new helm-chart view.
func NewHelmChart(gvr client.GVR) ResourceViewer {
c := HelmChart{
ResourceViewer: NewBrowser(gvr),
ResourceViewer: NewValueExtender(NewBrowser(gvr)),
}
c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
c.GetTable().SetSelectedStyle(tcell.StyleDefault.
@ -46,7 +42,6 @@ func (c *HelmChart) bindKeys(aa ui.KeyActions) {
aa.Add(ui.KeyActions{
ui.KeyR: ui.NewKeyAction("Releases", c.historyCmd, true),
ui.KeyShiftS: ui.NewKeyAction("Sort Status", c.GetTable().SortColCmd(statusCol, true), false),
ui.KeyV: ui.NewKeyAction("Values", c.getValsCmd(), true),
})
}
@ -77,35 +72,3 @@ func (c *HelmChart) helmContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyPath, path)
}
func (c *HelmChart) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey {
return func(evt *tcell.EventKey) *tcell.EventKey {
path := c.GetTable().GetSelectedItem()
if path == "" {
return evt
}
c.Values = model.NewValues(c.GVR(), path)
v := NewLiveView(c.App(), "Values", c.Values)
v.actions.Add(ui.KeyActions{
ui.KeyV: ui.NewKeyAction("Toggle All Values", c.toggleValuesCmd, true),
})
if err := v.app.inject(v, false); err != nil {
v.app.Flash().Err(err)
}
return nil
}
}
func (c *HelmChart) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey {
c.Values.ToggleValues()
if err := c.Values.Refresh(c.defaultCtx()); err != nil {
log.Error().Err(err).Msgf("helm refresh failed")
return nil
}
c.App().Flash().Infof("Values toggled")
return nil
}
func (c *HelmChart) defaultCtx() context.Context {
return context.WithValue(context.Background(), internal.KeyFactory, c.App().factory)
}

View File

@ -21,10 +21,10 @@ type History struct {
ResourceViewer
}
// NewHelm returns a new alias view.
// NewHistory returns a new helm-history view.
func NewHistory(gvr client.GVR) ResourceViewer {
h := History{
ResourceViewer: NewBrowser(gvr),
ResourceViewer: NewValueExtender(NewBrowser(gvr)),
}
h.GetTable().SetColorerFn(helm.History{}.ColorerFunc())
h.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
@ -35,7 +35,7 @@ func NewHistory(gvr client.GVR) ResourceViewer {
return &h
}
// Init initializes the vie
// Init initializes the view
func (h *History) Init(ctx context.Context) error {
if err := h.ResourceViewer.Init(ctx); err != nil {
return err

View File

@ -0,0 +1,79 @@
package view
import (
"context"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2"
"github.com/rs/zerolog/log"
)
// ValueExtender adds values actions to a given viewer.
type ValueExtender struct {
ResourceViewer
}
// NewValueExtender returns a new extender.
func NewValueExtender(r ResourceViewer) ResourceViewer {
p := ValueExtender{ResourceViewer: r}
p.AddBindKeysFn(p.bindKeys)
p.GetTable().SetEnterFn(func(app *App, model ui.Tabular, gvr, path string) {
p.valuesCmd(nil)
})
return &p
}
func (v *ValueExtender) bindKeys(aa ui.KeyActions) {
aa.Add(ui.KeyActions{
ui.KeyV: ui.NewKeyAction("Values", v.valuesCmd, true),
})
}
func (v *ValueExtender) valuesCmd(evt *tcell.EventKey) *tcell.EventKey {
path := v.GetTable().GetSelectedItem()
if path == "" {
return evt
}
showValues(v.defaultCtx(), v.App(), path, v.GVR())
return nil
}
func (v *ValueExtender) defaultCtx() context.Context {
return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory)
}
func showValues(ctx context.Context, app *App, path string, gvr client.GVR) {
vm := model.NewValues(gvr, path)
if err := vm.Init(app.factory); err != nil {
app.Flash().Errf("Initializing the values model failed: %s", err)
}
toggleValuesCmd := func(evt *tcell.EventKey) *tcell.EventKey {
if err := vm.ToggleValues(); err != nil {
app.Flash().Errf("Values toggle failed: %s", err)
return nil
}
if err := vm.Refresh(ctx); err != nil {
log.Error().Err(err).Msgf("values refresh failed")
return nil
}
app.Flash().Infof("Values toggled")
return nil
}
v := NewLiveView(app, "Values", vm)
v.actions.Add(ui.KeyActions{
ui.KeyV: ui.NewKeyAction("Toggle All Values", toggleValuesCmd, true),
})
if err := v.app.inject(v, false); err != nil {
v.app.Flash().Err(err)
}
}