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 extendermine
parent
ca72490350
commit
7bd7ec766a
|
|
@ -21,6 +21,7 @@ var (
|
|||
_ Accessor = (*HelmChart)(nil)
|
||||
_ Nuker = (*HelmChart)(nil)
|
||||
_ Describer = (*HelmChart)(nil)
|
||||
_ Valuer = (*HelmChart)(nil)
|
||||
)
|
||||
|
||||
// HelmChart represents a helm chart.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue