Skip to content

Commit 69cd0cd

Browse files
authored
K9s/release v0.32.1 (#2591)
* [Bug] Fix #2579 * [Bug] Fix #2584 * [Exp] make pf address configurable via K9S_DEFAULT_PF_ADDRESS * v0.32.1 release
1 parent 6c6fc22 commit 69cd0cd

File tree

11 files changed

+146
-63
lines changed

11 files changed

+146
-63
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
1111
else
1212
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
1313
endif
14-
VERSION ?= v0.32.0
14+
VERSION ?= v0.32.1
1515
IMG_NAME := derailed/k9s
1616
IMAGE := ${IMG_NAME}:${VERSION}
1717

change_logs/release_v0.32.1.md

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
2+
3+
# Release v0.32.1
4+
5+
## Notes
6+
7+
Thank you to all that contributed with flushing out issues and enhancements for K9s!
8+
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
9+
and see if we're happier with some of the fixes!
10+
If you've filed an issue please help me verify and close.
11+
12+
Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
13+
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
14+
15+
As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
16+
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
17+
18+
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
19+
20+
## Maintenance Release!
21+
22+
The aftermath ;(
23+
24+
---
25+
26+
## Videos Are In The Can!
27+
28+
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
29+
30+
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
31+
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
32+
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
33+
34+
---
35+
36+
## Resolved Issues
37+
38+
* [#2584](https://github.com/derailed/k9s/issues/2584) Transfer of file doesn't detect corruption
39+
* [#2579](https://github.com/derailed/k9s/issues/2579) Default sorting behavior changed to descending sort bug
40+
41+
---
42+
43+
## Contributed PRs
44+
45+
Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
46+
47+
* [#2586](https://github.com/derailed/k9s/pull/2586) Properly initialize key actions in picker
48+
49+
---
50+
51+
<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)

internal/config/data/context.go

+8-13
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ import (
1010
"k8s.io/client-go/tools/clientcmd/api"
1111
)
1212

13-
// DefaultPFAddress specifies the default PortForward host address.
14-
const DefaultPFAddress = "localhost"
15-
1613
// Context tracks K9s context configuration.
1714
type Context struct {
1815
ClusterName string `yaml:"cluster,omitempty"`
@@ -30,20 +27,18 @@ func NewContext() *Context {
3027
return &Context{
3128
Namespace: NewNamespace(),
3229
View: NewView(),
33-
PortForwardAddress: DefaultPFAddress,
30+
PortForwardAddress: defaultPFAddress(),
3431
FeatureGates: NewFeatureGates(),
3532
}
3633
}
3734

3835
// NewContextFromConfig returns a config based on a kubecontext.
3936
func NewContextFromConfig(cfg *api.Context) *Context {
40-
return &Context{
41-
Namespace: NewActiveNamespace(cfg.Namespace),
42-
ClusterName: cfg.Cluster,
43-
View: NewView(),
44-
PortForwardAddress: DefaultPFAddress,
45-
FeatureGates: NewFeatureGates(),
46-
}
37+
ct := NewContext()
38+
ct.Namespace, ct.ClusterName = NewActiveNamespace(cfg.Namespace), cfg.Cluster
39+
40+
return ct
41+
4742
}
4843

4944
// NewContextFromKubeConfig returns a new instance based on kubesettings or an error.
@@ -61,8 +56,8 @@ func (c *Context) merge(old *Context) {
6156
return
6257
}
6358
c.Namespace.merge(old.Namespace)
64-
6559
}
60+
6661
func (c *Context) GetClusterName() string {
6762
c.mx.RLock()
6863
defer c.mx.RUnlock()
@@ -76,7 +71,7 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) {
7671
defer c.mx.Unlock()
7772

7873
if c.PortForwardAddress == "" {
79-
c.PortForwardAddress = DefaultPFAddress
74+
c.PortForwardAddress = defaultPFAddress()
8075
}
8176
if cl, err := ks.CurrentClusterName(); err == nil {
8277
c.ClusterName = cl

internal/config/data/helpers.go

+13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import (
1111
"regexp"
1212
)
1313

14+
const (
15+
envPFAddress = "K9S_DEFAULT_PF_ADDRESS"
16+
defaultPortFwdAddress = "localhost"
17+
)
18+
1419
var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)
1520

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

31+
func defaultPFAddress() string {
32+
if a := os.Getenv(envPFAddress); a != "" {
33+
return a
34+
}
35+
36+
return defaultPortFwdAddress
37+
}
38+
2639
// InList check if string is in a collection of strings.
2740
func InList(ll []string, n string) bool {
2841
for _, l := range ll {

internal/dao/registry.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
101101
client.NewGVR("v1/pods"): &Pod{},
102102
client.NewGVR("v1/nodes"): &Node{},
103103
client.NewGVR("v1/namespaces"): &Namespace{},
104-
client.NewGVR("v1/configmap"): &ConfigMap{},
104+
client.NewGVR("v1/configmaps"): &ConfigMap{},
105105
client.NewGVR("v1/secrets"): &Secret{},
106106
client.NewGVR("apps/v1/deployments"): &Deployment{},
107107
client.NewGVR("apps/v1/daemonsets"): &DaemonSet{},

internal/model1/table_data.go

+1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ func (t *TableData) sortCol(vs *config.ViewSetting) (SortColumn, error) {
376376
psc.Name = t.header[0].Name
377377
}
378378
}
379+
psc.ASC = true
379380

380381
return psc, nil
381382
}

internal/ui/dialog/transfer.go

+33-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package dialog
55

66
import (
7+
"strconv"
78
"strings"
89

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

1415
const confirmKey = "confirm"
1516

16-
type TransferFn func(from, to, co string, download, no_preserve bool) bool
17+
type TransferFn func(TransferArgs) bool
18+
19+
type TransferArgs struct {
20+
From, To, CO string
21+
Download, NoPreserve bool
22+
Retries int
23+
}
1724

1825
type TransferDialogOpts struct {
1926
Containers []string
2027
Pod string
2128
Title, Message string
29+
Retries int
2230
Ack TransferFn
2331
Cancel cancelFunc
2432
}
@@ -38,44 +46,49 @@ func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts)
3846

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

41-
from, to := opts.Pod, ""
49+
args := TransferArgs{
50+
From: opts.Pod,
51+
Retries: opts.Retries,
52+
}
4253
var fromField, toField *tview.InputField
43-
download := true
44-
f.AddCheckbox("Download:", download, func(_ string, flag bool) {
54+
args.Download = true
55+
f.AddCheckbox("Download:", args.Download, func(_ string, flag bool) {
4556
if flag {
4657
modal.SetText(strings.Replace(opts.Message, "Upload", "Download", 1))
4758
} else {
4859
modal.SetText(strings.Replace(opts.Message, "Download", "Upload", 1))
4960
}
50-
download = flag
51-
from, to = to, from
52-
fromField.SetText(from)
53-
toField.SetText(to)
61+
args.Download = flag
62+
args.From, args.To = args.To, args.From
63+
fromField.SetText(args.From)
64+
toField.SetText(args.To)
5465
})
5566

56-
f.AddInputField("From:", from, 40, nil, func(t string) {
57-
from = t
67+
f.AddInputField("From:", args.From, 40, nil, func(v string) {
68+
args.From = v
5869
})
59-
f.AddInputField("To:", to, 40, nil, func(t string) {
60-
to = t
70+
f.AddInputField("To:", args.To, 40, nil, func(v string) {
71+
args.To = v
6172
})
6273
fromField, _ = f.GetFormItemByLabel("From:").(*tview.InputField)
6374
toField, _ = f.GetFormItemByLabel("To:").(*tview.InputField)
6475

65-
var no_preserve bool
66-
f.AddCheckbox("NoPreserve:", no_preserve, func(_ string, f bool) {
67-
no_preserve = f
76+
f.AddCheckbox("NoPreserve:", args.NoPreserve, func(_ string, f bool) {
77+
args.NoPreserve = f
6878
})
69-
var co string
7079
if len(opts.Containers) > 0 {
71-
co = opts.Containers[0]
80+
args.CO = opts.Containers[0]
7281
}
73-
f.AddInputField("Container:", co, 30, nil, func(t string) {
74-
co = t
82+
f.AddInputField("Container:", args.CO, 30, nil, func(v string) {
83+
args.CO = v
84+
})
85+
retries := strconv.Itoa(opts.Retries)
86+
f.AddInputField("Retries:", retries, 30, nil, func(v string) {
87+
retries = v
7588
})
7689

7790
f.AddButton("OK", func() {
78-
if !opts.Ack(from, to, co, download, no_preserve) {
91+
if !opts.Ack(args) {
7992
return
8093
}
8194
dismissConfirm(pages)

internal/view/exec.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ func runK(a *App, opts shellOpts) error {
7979
}
8080
opts.binary = bin
8181

82-
suspended, errChan, _ := run(a, opts)
82+
suspended, errChan, stChan := run(a, opts)
8383
if !suspended {
8484
return fmt.Errorf("unable to run command")
8585
}
86+
for v := range stChan {
87+
log.Debug().Msgf(" - %s", v)
88+
}
8689
var errs error
8790
for e := range errChan {
8891
errs = errors.Join(errs, e)
@@ -474,7 +477,7 @@ func asResource(r config.Limits) v1.ResourceRequirements {
474477
}
475478
}
476479

477-
func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.Writer, cmds ...*exec.Cmd) error {
480+
func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *bytes.Buffer, cmds ...*exec.Cmd) error {
478481
if len(cmds) == 0 {
479482
return nil
480483
}
@@ -487,6 +490,11 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.W
487490
if err := cmd.Run(); err != nil {
488491
log.Error().Err(err).Msgf("Command failed: %s", err)
489492
} else {
493+
for _, l := range strings.Split(w.String(), "\n") {
494+
if l != "" {
495+
statusChan <- fmt.Sprintf("[output] %s", l)
496+
}
497+
}
490498
statusChan <- fmt.Sprintf("Command completed successfully: %q", render.Truncate(cmd.String(), 20))
491499
log.Info().Msgf("Command completed successfully: %q", cmd.String())
492500
}

internal/view/pf_dialog.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
3838
log.Error().Err(err).Msgf("No active context detected")
3939
return
4040
}
41-
address := ct.PortForwardAddress
4241

4342
pf, err := aa.PreferredPorts(ports)
4443
if err != nil {
@@ -62,6 +61,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
6261
if loField.GetText() == "" {
6362
loField.SetPlaceholder("Enter a local port")
6463
}
64+
address := ct.PortForwardAddress
6565
f.AddInputField("Address:", address, fieldLen, nil, func(h string) {
6666
address = h
6767
})

internal/view/pod.go

+26-24
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ import (
3030
)
3131

3232
const (
33-
windowsOS = "windows"
34-
powerShell = "powershell"
35-
osSelector = "kubernetes.io/os"
36-
osBetaSelector = "beta." + osSelector
37-
trUpload = "Upload"
38-
trDownload = "Download"
39-
pfIndicator = "[orange::b]Ⓕ"
33+
windowsOS = "windows"
34+
powerShell = "powershell"
35+
osSelector = "kubernetes.io/os"
36+
osBetaSelector = "beta." + osSelector
37+
trUpload = "Upload"
38+
trDownload = "Download"
39+
pfIndicator = "[orange::b]Ⓕ"
40+
defaultTxRetries = 999
4041
)
4142

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

312313
ns, n := client.Namespaced(path)
313-
ack := func(from, to, co string, download, no_preserve bool) bool {
314-
local := to
315-
if !download {
316-
local = from
314+
ack := func(args dialog.TransferArgs) bool {
315+
local := args.To
316+
if !args.Download {
317+
local = args.From
317318
}
318-
if _, err := os.Stat(local); !download && errors.Is(err, fs.ErrNotExist) {
319+
if _, err := os.Stat(local); !args.Download && errors.Is(err, fs.ErrNotExist) {
319320
p.App().Flash().Err(err)
320321
return false
321322
}
322323

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

332-
opts := shellOpts{
333+
cliOpts := shellOpts{
333334
background: true,
334-
args: args,
335+
args: opts,
335336
}
336337
op := trUpload
337-
if download {
338+
if args.Download {
338339
op = trDownload
339340
}
340341

341-
fqn := path + ":" + co
342-
if err := runK(p.App(), opts); err != nil {
342+
fqn := path + ":" + args.CO
343+
if err := runK(p.App(), cliOpts); err != nil {
343344
p.App().cowCmd(err.Error())
344345
} else {
345346
p.App().Flash().Infof("%s successful on %s!", op, fqn)
@@ -359,6 +360,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey {
359360
Message: "Download Files",
360361
Pod: fmt.Sprintf("%s/%s:", ns, n),
361362
Ack: ack,
363+
Retries: defaultTxRetries,
362364
Cancel: func() {},
363365
}
364366
dialog.ShowUploads(p.App().Styles.Dialog(), p.App().Content.Pages, opts)

0 commit comments

Comments
 (0)