Skip to content

Commit

Permalink
K9s/release v0.32.1 (#2591)
Browse files Browse the repository at this point in the history
* [Bug] Fix #2579

* [Bug] Fix #2584

* [Exp] make pf address configurable via K9S_DEFAULT_PF_ADDRESS

* v0.32.1 release
  • Loading branch information
derailed committed Mar 5, 2024
1 parent 6c6fc22 commit 69cd0cd
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 63 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
else
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
endif
VERSION ?= v0.32.0
VERSION ?= v0.32.1
IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION}

Expand Down
51 changes: 51 additions & 0 deletions change_logs/release_v0.32.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

# Release v0.32.1

## Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)

## Maintenance Release!

The aftermath ;(

---

## Videos Are In The Can!

Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...

* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)

---

## Resolved Issues

* [#2584](https://github.com/derailed/k9s/issues/2584) Transfer of file doesn't detect corruption
* [#2579](https://github.com/derailed/k9s/issues/2579) Default sorting behavior changed to descending sort bug

---

## Contributed PRs

Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!

* [#2586](https://github.com/derailed/k9s/pull/2586) Properly initialize key actions in picker

---

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
21 changes: 8 additions & 13 deletions internal/config/data/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import (
"k8s.io/client-go/tools/clientcmd/api"
)

// DefaultPFAddress specifies the default PortForward host address.
const DefaultPFAddress = "localhost"

// Context tracks K9s context configuration.
type Context struct {
ClusterName string `yaml:"cluster,omitempty"`
Expand All @@ -30,20 +27,18 @@ func NewContext() *Context {
return &Context{
Namespace: NewNamespace(),
View: NewView(),
PortForwardAddress: DefaultPFAddress,
PortForwardAddress: defaultPFAddress(),
FeatureGates: NewFeatureGates(),
}
}

// NewContextFromConfig returns a config based on a kubecontext.
func NewContextFromConfig(cfg *api.Context) *Context {
return &Context{
Namespace: NewActiveNamespace(cfg.Namespace),
ClusterName: cfg.Cluster,
View: NewView(),
PortForwardAddress: DefaultPFAddress,
FeatureGates: NewFeatureGates(),
}
ct := NewContext()
ct.Namespace, ct.ClusterName = NewActiveNamespace(cfg.Namespace), cfg.Cluster

return ct

}

// NewContextFromKubeConfig returns a new instance based on kubesettings or an error.
Expand All @@ -61,8 +56,8 @@ func (c *Context) merge(old *Context) {
return
}
c.Namespace.merge(old.Namespace)

}

func (c *Context) GetClusterName() string {
c.mx.RLock()
defer c.mx.RUnlock()
Expand All @@ -76,7 +71,7 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) {
defer c.mx.Unlock()

if c.PortForwardAddress == "" {
c.PortForwardAddress = DefaultPFAddress
c.PortForwardAddress = defaultPFAddress()
}
if cl, err := ks.CurrentClusterName(); err == nil {
c.ClusterName = cl
Expand Down
13 changes: 13 additions & 0 deletions internal/config/data/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"regexp"
)

const (
envPFAddress = "K9S_DEFAULT_PF_ADDRESS"
defaultPortFwdAddress = "localhost"
)

var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)

// SanitizeContextSubpath ensure cluster/context produces a valid path.
Expand All @@ -23,6 +28,14 @@ func SanitizeFileName(name string) string {
return invalidPathCharsRX.ReplaceAllString(name, "-")
}

func defaultPFAddress() string {
if a := os.Getenv(envPFAddress); a != "" {
return a
}

return defaultPortFwdAddress
}

// InList check if string is in a collection of strings.
func InList(ll []string, n string) bool {
for _, l := range ll {
Expand Down
2 changes: 1 addition & 1 deletion internal/dao/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
client.NewGVR("v1/pods"): &Pod{},
client.NewGVR("v1/nodes"): &Node{},
client.NewGVR("v1/namespaces"): &Namespace{},
client.NewGVR("v1/configmap"): &ConfigMap{},
client.NewGVR("v1/configmaps"): &ConfigMap{},
client.NewGVR("v1/secrets"): &Secret{},
client.NewGVR("apps/v1/deployments"): &Deployment{},
client.NewGVR("apps/v1/daemonsets"): &DaemonSet{},
Expand Down
1 change: 1 addition & 0 deletions internal/model1/table_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ func (t *TableData) sortCol(vs *config.ViewSetting) (SortColumn, error) {
psc.Name = t.header[0].Name
}
}
psc.ASC = true

return psc, nil
}
Expand Down
53 changes: 33 additions & 20 deletions internal/ui/dialog/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package dialog

import (
"strconv"
"strings"

"github.com/derailed/k9s/internal/config"
Expand All @@ -13,12 +14,19 @@ import (

const confirmKey = "confirm"

type TransferFn func(from, to, co string, download, no_preserve bool) bool
type TransferFn func(TransferArgs) bool

type TransferArgs struct {
From, To, CO string
Download, NoPreserve bool
Retries int
}

type TransferDialogOpts struct {
Containers []string
Pod string
Title, Message string
Retries int
Ack TransferFn
Cancel cancelFunc
}
Expand All @@ -38,44 +46,49 @@ func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts)

modal := tview.NewModalForm("<"+opts.Title+">", f)

from, to := opts.Pod, ""
args := TransferArgs{
From: opts.Pod,
Retries: opts.Retries,
}
var fromField, toField *tview.InputField
download := true
f.AddCheckbox("Download:", download, func(_ string, flag bool) {
args.Download = true
f.AddCheckbox("Download:", args.Download, func(_ string, flag bool) {
if flag {
modal.SetText(strings.Replace(opts.Message, "Upload", "Download", 1))
} else {
modal.SetText(strings.Replace(opts.Message, "Download", "Upload", 1))
}
download = flag
from, to = to, from
fromField.SetText(from)
toField.SetText(to)
args.Download = flag
args.From, args.To = args.To, args.From
fromField.SetText(args.From)
toField.SetText(args.To)
})

f.AddInputField("From:", from, 40, nil, func(t string) {
from = t
f.AddInputField("From:", args.From, 40, nil, func(v string) {
args.From = v
})
f.AddInputField("To:", to, 40, nil, func(t string) {
to = t
f.AddInputField("To:", args.To, 40, nil, func(v string) {
args.To = v
})
fromField, _ = f.GetFormItemByLabel("From:").(*tview.InputField)
toField, _ = f.GetFormItemByLabel("To:").(*tview.InputField)

var no_preserve bool
f.AddCheckbox("NoPreserve:", no_preserve, func(_ string, f bool) {
no_preserve = f
f.AddCheckbox("NoPreserve:", args.NoPreserve, func(_ string, f bool) {
args.NoPreserve = f
})
var co string
if len(opts.Containers) > 0 {
co = opts.Containers[0]
args.CO = opts.Containers[0]
}
f.AddInputField("Container:", co, 30, nil, func(t string) {
co = t
f.AddInputField("Container:", args.CO, 30, nil, func(v string) {
args.CO = v
})
retries := strconv.Itoa(opts.Retries)
f.AddInputField("Retries:", retries, 30, nil, func(v string) {
retries = v
})

f.AddButton("OK", func() {
if !opts.Ack(from, to, co, download, no_preserve) {
if !opts.Ack(args) {
return
}
dismissConfirm(pages)
Expand Down
12 changes: 10 additions & 2 deletions internal/view/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,13 @@ func runK(a *App, opts shellOpts) error {
}
opts.binary = bin

suspended, errChan, _ := run(a, opts)
suspended, errChan, stChan := run(a, opts)
if !suspended {
return fmt.Errorf("unable to run command")
}
for v := range stChan {
log.Debug().Msgf(" - %s", v)
}
var errs error
for e := range errChan {
errs = errors.Join(errs, e)
Expand Down Expand Up @@ -474,7 +477,7 @@ func asResource(r config.Limits) v1.ResourceRequirements {
}
}

func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.Writer, cmds ...*exec.Cmd) error {
func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *bytes.Buffer, cmds ...*exec.Cmd) error {
if len(cmds) == 0 {
return nil
}
Expand All @@ -487,6 +490,11 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.W
if err := cmd.Run(); err != nil {
log.Error().Err(err).Msgf("Command failed: %s", err)
} else {
for _, l := range strings.Split(w.String(), "\n") {
if l != "" {
statusChan <- fmt.Sprintf("[output] %s", l)
}
}
statusChan <- fmt.Sprintf("Command completed successfully: %q", render.Truncate(cmd.String(), 20))
log.Info().Msgf("Command completed successfully: %q", cmd.String())
}
Expand Down
2 changes: 1 addition & 1 deletion internal/view/pf_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
log.Error().Err(err).Msgf("No active context detected")
return
}
address := ct.PortForwardAddress

pf, err := aa.PreferredPorts(ports)
if err != nil {
Expand All @@ -62,6 +61,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
if loField.GetText() == "" {
loField.SetPlaceholder("Enter a local port")
}
address := ct.PortForwardAddress
f.AddInputField("Address:", address, fieldLen, nil, func(h string) {
address = h
})
Expand Down
50 changes: 26 additions & 24 deletions internal/view/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ import (
)

const (
windowsOS = "windows"
powerShell = "powershell"
osSelector = "kubernetes.io/os"
osBetaSelector = "beta." + osSelector
trUpload = "Upload"
trDownload = "Download"
pfIndicator = "[orange::b]Ⓕ"
windowsOS = "windows"
powerShell = "powershell"
osSelector = "kubernetes.io/os"
osBetaSelector = "beta." + osSelector
trUpload = "Upload"
trDownload = "Download"
pfIndicator = "[orange::b]Ⓕ"
defaultTxRetries = 999
)

// Pod represents a pod viewer.
Expand Down Expand Up @@ -310,36 +311,36 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
}

ns, n := client.Namespaced(path)
ack := func(from, to, co string, download, no_preserve bool) bool {
local := to
if !download {
local = from
ack := func(args dialog.TransferArgs) bool {
local := args.To
if !args.Download {
local = args.From
}
if _, err := os.Stat(local); !download && errors.Is(err, fs.ErrNotExist) {
if _, err := os.Stat(local); !args.Download && errors.Is(err, fs.ErrNotExist) {
p.App().Flash().Err(err)
return false
}

args := make([]string, 0, 10)
args = append(args, "cp")
args = append(args, strings.TrimSpace(from))
args = append(args, strings.TrimSpace(to))
args = append(args, fmt.Sprintf("--no-preserve=%t", no_preserve))
if co != "" {
args = append(args, "-c="+co)
opts := make([]string, 0, 10)
opts = append(opts, "cp")
opts = append(opts, strings.TrimSpace(args.From))
opts = append(opts, strings.TrimSpace(args.To))
opts = append(opts, fmt.Sprintf("--no-preserve=%t", args.NoPreserve))
if args.CO != "" {
opts = append(opts, "-c="+args.CO)
}

opts := shellOpts{
cliOpts := shellOpts{
background: true,
args: args,
args: opts,
}
op := trUpload
if download {
if args.Download {
op = trDownload
}

fqn := path + ":" + co
if err := runK(p.App(), opts); err != nil {
fqn := path + ":" + args.CO
if err := runK(p.App(), cliOpts); err != nil {
p.App().cowCmd(err.Error())
} else {
p.App().Flash().Infof("%s successful on %s!", op, fqn)
Expand All @@ -359,6 +360,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
Message: "Download Files",
Pod: fmt.Sprintf("%s/%s:", ns, n),
Ack: ack,
Retries: defaultTxRetries,
Cancel: func() {},
}
dialog.ShowUploads(p.App().Styles.Dialog(), p.App().Content.Pages, opts)
Expand Down
Loading

0 comments on commit 69cd0cd

Please sign in to comment.