k9s/internal/view/image_extender.go

172 lines
4.4 KiB
Go

package view
import (
"context"
"fmt"
"strings"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
corev1 "k8s.io/api/core/v1"
)
const imageKey = "setImage"
type imageFormSpec struct {
name, dockerImage, newDockerImage string
init bool
}
func (m *imageFormSpec) modified() bool {
var newDockerImage = strings.TrimSpace(m.newDockerImage)
return newDockerImage != "" && m.dockerImage != newDockerImage
}
func (m *imageFormSpec) imageSpec() dao.ImageSpec {
var ret = dao.ImageSpec{
Name: m.name,
Init: m.init,
}
if m.modified() {
ret.DockerImage = strings.TrimSpace(m.newDockerImage)
} else {
ret.DockerImage = m.dockerImage
}
return ret
}
// ImageExtender provides for overriding container images.
type ImageExtender struct {
ResourceViewer
}
func NewImageExtender(r ResourceViewer) ResourceViewer {
s := ImageExtender{ResourceViewer: r}
s.AddBindKeysFn(s.bindKeys)
return &s
}
func (s *ImageExtender) bindKeys(aa ui.KeyActions) {
if s.App().Config.K9s.IsReadOnly() {
return
}
aa.Add(ui.KeyActions{
ui.KeyI: ui.NewKeyAction("Set Image", s.setImageCmd, false),
})
}
func (s *ImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey {
path := s.GetTable().GetSelectedItem()
if path == "" {
return nil
}
s.Stop()
defer s.Start()
s.showImageDialog(path)
return nil
}
func (s *ImageExtender) showImageDialog(path string) {
confirm := tview.NewModalForm("<Set image>", s.makeSetImageForm(path))
confirm.SetText(fmt.Sprintf("Set image %s %s", s.GVR(), path))
confirm.SetDoneFunc(func(int, string) {
s.dismissDialog()
})
s.App().Content.AddPage(imageKey, confirm, false, false)
s.App().Content.ShowPage(imageKey)
}
func (s *ImageExtender) makeSetImageForm(sel string) *tview.Form {
f := s.makeStyledForm()
podSpec, err := s.getPodSpec(sel)
if err != nil {
s.App().Flash().Err(err)
return nil
}
formContainerLines := make([]*imageFormSpec, len(podSpec.InitContainers)+len(podSpec.Containers))
for _, spec := range podSpec.InitContainers {
formContainerLines = append(formContainerLines, &imageFormSpec{init: true, name: spec.Name, dockerImage: spec.Image})
}
for _, spec := range podSpec.Containers {
formContainerLines = append(formContainerLines, &imageFormSpec{name: spec.Name, dockerImage: spec.Image})
}
for i := range formContainerLines {
ctn := formContainerLines[i]
f.AddInputField(ctn.name, ctn.dockerImage, 0, nil, func(changed string) {
ctn.newDockerImage = changed
})
}
f.AddButton("OK", func() {
defer s.dismissDialog()
var imageSpecsModified dao.ImageSpecs
for _, v := range formContainerLines {
if v.modified() {
imageSpecsModified = append(imageSpecsModified, v.imageSpec())
}
}
ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout())
defer cancel()
if err := s.setImages(ctx, sel, imageSpecsModified); err != nil {
log.Error().Err(err).Msgf("PodSpec %s image update failed", sel)
s.App().Flash().Err(err)
return
}
s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), sel)
})
f.AddButton("Cancel", func() {
s.dismissDialog()
})
return f
}
func (s *ImageExtender) dismissDialog() {
s.App().Content.RemovePage(imageKey)
}
func (s *ImageExtender) makeStyledForm() *tview.Form {
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor).
SetButtonTextColor(tview.Styles.PrimaryTextColor).
SetLabelColor(tcell.ColorAqua).
SetFieldTextColor(tcell.ColorOrange)
return f
}
func (s *ImageExtender) getPodSpec(path string) (*corev1.PodSpec, error) {
res, err := dao.AccessorFor(s.App().factory, s.GVR())
if err != nil {
return nil, err
}
resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
if !ok {
return nil, fmt.Errorf("expecting a ContainsPodSpec for %q but got %T", s.GVR(), res)
}
return resourceWPodSpec.GetPodSpec(path)
}
func (s *ImageExtender) setImages(ctx context.Context, path string, imageSpecs dao.ImageSpecs) error {
res, err := dao.AccessorFor(s.App().factory, s.GVR())
if err != nil {
return err
}
resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
if !ok {
return fmt.Errorf("expecting a scalable resource for %q", s.GVR())
}
return resourceWPodSpec.SetImages(ctx, path, imageSpecs)
}