k9s/internal/view/dir.go

276 lines
5.9 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package view
import (
"context"
"fmt"
"os"
"path"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/ui/dialog"
"github.com/derailed/tcell/v2"
)
const (
kustomize = "kustomization"
kustomizeNoExt = "Kustomization"
kustomizeYAML = kustomize + extYAML
kustomizeYML = kustomize + extYML
extYAML = ".yaml"
extYML = ".yml"
)
// Dir represents a command directory view.
type Dir struct {
ResourceViewer
path string
}
// NewDir returns a new instance.
func NewDir(path string) ResourceViewer {
d := Dir{
ResourceViewer: NewBrowser(client.NewGVR("dir")),
path: path,
}
d.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
d.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorAliceBlue).Attributes(tcell.AttrNone))
d.AddBindKeysFn(d.bindKeys)
d.SetContextFn(d.dirContext)
return &d
}
// Init initializes the view.
func (d *Dir) Init(ctx context.Context) error {
if err := d.ResourceViewer.Init(ctx); err != nil {
return err
}
return nil
}
func (d *Dir) dirContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyPath, d.path)
}
func (d *Dir) bindDangerousKeys(aa ui.KeyActions) {
aa.Add(ui.KeyActions{
ui.KeyA: ui.NewKeyAction("Apply", d.applyCmd, true),
ui.KeyD: ui.NewKeyAction("Delete", d.delCmd, true),
ui.KeyE: ui.NewKeyAction("Edit", d.editCmd, true),
})
}
func (d *Dir) bindKeys(aa ui.KeyActions) {
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ)
if !d.App().Config.K9s.IsReadOnly() {
d.bindDangerousKeys(aa)
}
aa.Add(ui.KeyActions{
ui.KeyY: ui.NewKeyAction(yamlAction, d.viewCmd, true),
tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true),
})
}
func (d *Dir) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := d.GetTable().GetSelectedItem()
if sel == "" {
return evt
}
if path.Ext(sel) == "" {
return nil
}
yaml, err := os.ReadFile(sel)
if err != nil {
d.App().Flash().Err(err)
return nil
}
details := NewDetails(d.App(), yamlAction, sel, contentYAML, true).Update(string(yaml))
if err := d.App().inject(details, false); err != nil {
d.App().Flash().Err(err)
}
return nil
}
func isManifest(s string) bool {
ext := path.Ext(s)
return ext == ".yml" || ext == ".yaml"
}
func (d *Dir) editCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := d.GetTable().GetSelectedItem()
if sel == "" {
return evt
}
if !isManifest(sel) {
d.App().Flash().Errf("you must select a manifest")
return nil
}
d.Stop()
defer d.Start()
if !edit(d.App(), shellOpts{clear: true, args: []string{sel}}) {
d.App().Flash().Errf("Failed to launch editor")
}
return nil
}
func (d *Dir) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if d.GetTable().CmdBuff().IsActive() {
return d.GetTable().activateCmd(evt)
}
sel := d.GetTable().GetSelectedItem()
if sel == "" {
return evt
}
if isManifest(sel) {
d.App().Flash().Errf("you must select a directory")
return nil
}
v := NewDir(sel)
if err := d.App().inject(v, false); err != nil {
d.App().Flash().Err(err)
}
return evt
}
func isKustomized(sel string) bool {
if isManifest(sel) {
return false
}
ff, err := os.ReadDir(sel)
if err != nil {
return false
}
kk := []string{kustomizeNoExt, kustomizeYAML, kustomizeYML}
for _, f := range ff {
if config.InList(kk, f.Name()) {
return true
}
}
return false
}
func containsDir(sel string) bool {
if isManifest(sel) {
return false
}
ff, err := os.ReadDir(sel)
if err != nil {
return false
}
for _, f := range ff {
if f.IsDir() {
return true
}
}
return false
}
func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := d.GetTable().GetSelectedItem()
if sel == "" {
return evt
}
opts := []string{"-f"}
if containsDir(sel) {
opts = append(opts, "-R")
}
if isKustomized(sel) {
opts = []string{"-k"}
}
d.Stop()
defer d.Start()
{
args := make([]string, 0, 10)
args = append(args, "apply")
args = append(args, opts...)
args = append(args, sel)
res, err := runKu(d.App(), shellOpts{clear: false, args: args})
if err != nil {
res = "status:\n " + err.Error() + "\nmessage:\n" + fmtResults(res)
} else {
res = "message:\n" + fmtResults(res)
}
details := NewDetails(d.App(), "Applied Manifest", sel, contentYAML, true).Update(res)
if err := d.App().inject(details, false); err != nil {
d.App().Flash().Err(err)
}
}
return nil
}
func (d *Dir) delCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := d.GetTable().GetSelectedItem()
if sel == "" {
return evt
}
opts := []string{"-f"}
msgResource := "manifest"
if containsDir(sel) {
opts = append(opts, "-R")
}
if isKustomized(sel) {
opts = []string{"-k"}
msgResource = "kustomization"
}
d.Stop()
defer d.Start()
msg := fmt.Sprintf("Delete resource(s) in %s %s", msgResource, sel)
dialog.ShowConfirm(d.App().Styles.Dialog(), d.App().Content.Pages, "Confirm Delete", msg, func() {
args := make([]string, 0, 10)
args = append(args, "delete")
args = append(args, opts...)
args = append(args, sel)
res, err := runKu(d.App(), shellOpts{clear: false, args: args})
if err != nil {
res = "status:\n " + err.Error() + "\nmessage:\n" + fmtResults(res)
} else {
res = "message:\n" + fmtResults(res)
}
details := NewDetails(d.App(), "Deleted Manifest", sel, contentYAML, true).Update(res)
if err := d.App().inject(details, false); err != nil {
d.App().Flash().Err(err)
}
}, func() {})
return nil
}
func fmtResults(res string) string {
res = strings.TrimSpace(res)
lines := strings.Split(res, "\n")
ll := make([]string, 0, len(lines))
for _, l := range lines {
ll = append(ll, " "+l)
}
return strings.Join(ll, "\n")
}