parent
f4007fa0ce
commit
165ffeab93
2
Makefile
2
Makefile
|
|
@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME)
|
||||||
GIT_REV ?= $(shell git rev-parse --short HEAD)
|
GIT_REV ?= $(shell git rev-parse --short HEAD)
|
||||||
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
||||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
VERSION ?= v0.25.2
|
VERSION ?= v0.25.4
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
IMAGE := ${IMG_NAME}:${VERSION}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -278,12 +278,11 @@ K9s uses aliases to navigate most K8s resources.
|
||||||
|
|
||||||
## K9s Configuration
|
## K9s Configuration
|
||||||
|
|
||||||
K9s keeps its configurations inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. if `XDG_CONFIG_HOME` is unset, then K9s will default to look for its configuration in `$HOME/.config/k9s`. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from.
|
K9s keeps its configurations inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from.
|
||||||
|
|
||||||
|
| Unix | macOS | Windows |
|
||||||
| Unix | macOS | Windows |
|
|-----------------|------------------------------------|-----------------------|
|
||||||
|-----------------|-----------------------------|-----------------------|
|
| `~/.config/k9s` | `~/Library/Application Support/k9s` | `%LOCALAPPDATA%\k9s` |
|
||||||
| `~/.config/k9s` | `~/Library/Preferences/k9s` | `%LOCALAPPDATA%\k9s` |
|
|
||||||
|
|
||||||
> NOTE: This is still in flux and will change while in pre-release stage!
|
> NOTE: This is still in flux and will change while in pre-release stage!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||||
|
|
||||||
|
# Release v0.25.4
|
||||||
|
|
||||||
|
## 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!
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Issues
|
||||||
|
|
||||||
|
* [Issue #1319](https://github.com/derailed/k9s/issues/1319) Namespace filters are no longer applied on startup
|
||||||
|
* [Issue #1317](https://github.com/derailed/k9s/issues/1317) port forwarding broke with multiple exposed ports
|
||||||
|
* [Issue #1316](https://github.com/derailed/k9s/issues/1316) Configuration for macOS is using wrong path
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/adrg/xdg"
|
"github.com/adrg/xdg"
|
||||||
|
|
@ -60,14 +59,6 @@ func K9sHome() string {
|
||||||
if env := os.Getenv(K9sConfig); env != "" {
|
if env := os.Getenv(K9sConfig); env != "" {
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
if env := os.Getenv("XDG_CONFIG_HOME"); env == "" {
|
|
||||||
dir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msgf("user home dir")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return path.Join(dir, ".config", "k9s")
|
|
||||||
}
|
|
||||||
|
|
||||||
xdgK9sHome, err := xdg.ConfigFile("k9s")
|
xdgK9sHome, err := xdg.ConfigFile("k9s")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -116,6 +107,8 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
|
||||||
ns, override = *flags.Namespace, true
|
ns, override = *flags.Namespace, true
|
||||||
} else if context.Namespace != "" {
|
} else if context.Namespace != "" {
|
||||||
ns = context.Namespace
|
ns = context.Namespace
|
||||||
|
} else if cl := c.K9s.ActiveCluster(); cl != nil {
|
||||||
|
ns = cl.Namespace.Active
|
||||||
}
|
}
|
||||||
|
|
||||||
if ns != "" {
|
if ns != "" {
|
||||||
|
|
|
||||||
|
|
@ -129,12 +129,6 @@ func (f *FishBuff) fireSuggestionChanged(ss []string) {
|
||||||
} else {
|
} else {
|
||||||
suggest = ss[f.suggestionIndex]
|
suggest = ss[f.suggestionIndex]
|
||||||
}
|
}
|
||||||
f.SetText(f.GetText(), suggest)
|
f.SetText(f.GetText(), suggest)
|
||||||
|
|
||||||
// BOZO!!
|
|
||||||
//for _, l := range f.listeners {
|
|
||||||
// if listener, ok := l.(SuggestionListener); ok {
|
|
||||||
// listener.SuggestionChanged(text, sug)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,17 @@ func (c ContainerPortSpecs) Dump() string {
|
||||||
return strings.Join(ss, "\n")
|
return strings.Join(ss, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InSpecs checks if given port matches a spec.
|
||||||
|
func (c ContainerPortSpecs) MatchSpec(s string) bool {
|
||||||
|
for _, spec := range c {
|
||||||
|
if spec.MatchSpec(s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ToTunnels convert port specs to tunnels.
|
// ToTunnels convert port specs to tunnels.
|
||||||
func (c ContainerPortSpecs) ToTunnels(address string) PortTunnels {
|
func (c ContainerPortSpecs) ToTunnels(address string) PortTunnels {
|
||||||
tt := make(PortTunnels, 0, len(c))
|
tt := make(PortTunnels, 0, len(c))
|
||||||
|
|
@ -97,6 +108,15 @@ func NewPortSpec(co, portName string, port int32) ContainerPortSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c ContainerPortSpec) MatchSpec(s string) bool {
|
||||||
|
tokens := strings.Split(s, "::")
|
||||||
|
if len(tokens) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens[0] == c.Container && tokens[1] == c.PortNum
|
||||||
|
}
|
||||||
|
|
||||||
func (c ContainerPortSpec) ToTunnel(address string) PortTunnel {
|
func (c ContainerPortSpec) ToTunnel(address string) PortTunnel {
|
||||||
return PortTunnel{
|
return PortTunnel{
|
||||||
Address: address,
|
Address: address,
|
||||||
|
|
|
||||||
|
|
@ -127,14 +127,14 @@ func accessMode(aa []v1.PersistentVolumeAccessMode) string {
|
||||||
dd := accessDedup(aa)
|
dd := accessDedup(aa)
|
||||||
s := make([]string, 0, len(dd))
|
s := make([]string, 0, len(dd))
|
||||||
for _, am := range dd {
|
for _, am := range dd {
|
||||||
switch {
|
switch am {
|
||||||
case am == v1.ReadWriteOnce:
|
case v1.ReadWriteOnce:
|
||||||
s = append(s, "RWO")
|
s = append(s, "RWO")
|
||||||
case am == v1.ReadOnlyMany:
|
case v1.ReadOnlyMany:
|
||||||
s = append(s, "ROX")
|
s = append(s, "ROX")
|
||||||
case am == v1.ReadWriteMany:
|
case v1.ReadWriteMany:
|
||||||
s = append(s, "RWX")
|
s = append(s, "RWX")
|
||||||
case am == v1.ReadWriteOncePod:
|
case v1.ReadWriteOncePod:
|
||||||
s = append(s, "RWOP")
|
s = append(s, "RWOP")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func ShowError(styles config.Dialog, pages *ui.Pages, msg string) {
|
||||||
}
|
}
|
||||||
f.SetFocus(0)
|
f.SetFocus(0)
|
||||||
modal := tview.NewModalForm("<error>", f)
|
modal := tview.NewModalForm("<error>", f)
|
||||||
modal.SetText(cowTalk(f, msg))
|
modal.SetText(cowTalk(msg))
|
||||||
modal.SetTextColor(tcell.ColorOrangeRed)
|
modal.SetTextColor(tcell.ColorOrangeRed)
|
||||||
modal.SetDoneFunc(func(int, string) {
|
modal.SetDoneFunc(func(int, string) {
|
||||||
dismissError(pages)
|
dismissError(pages)
|
||||||
|
|
@ -41,7 +41,7 @@ func dismissError(pages *ui.Pages) {
|
||||||
pages.RemovePage(confirmKey)
|
pages.RemovePage(confirmKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cowTalk(f *tview.Form, says string) string {
|
func cowTalk(says string) string {
|
||||||
msg := fmt.Sprintf("< Ruroh? %s >", says)
|
msg := fmt.Sprintf("< Ruroh? %s >", says)
|
||||||
buff := make([]string, 0, len(cow)+3)
|
buff := make([]string, 0, len(cow)+3)
|
||||||
buff = append(buff, msg)
|
buff = append(buff, msg)
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,7 @@ func (a *App) Init(version string, rate int) error {
|
||||||
if a.Conn() == nil {
|
if a.Conn() == nil {
|
||||||
return errors.New("No client connection detected")
|
return errors.New("No client connection detected")
|
||||||
}
|
}
|
||||||
ns, err := a.Conn().Config().CurrentNamespaceName()
|
ns := a.Config.ActiveNamespace()
|
||||||
log.Debug().Msgf("CURRENT-NS %q -- %v", ns, err)
|
|
||||||
if err != nil {
|
|
||||||
log.Info().Msg("No namespace specified using cluster default namespace")
|
|
||||||
} else if err = a.Config.SetActiveNamespace(ns); err != nil {
|
|
||||||
log.Error().Err(err).Msgf("Fail to set active namespace to %q", ns)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.factory = watch.NewFactory(a.Conn())
|
a.factory = watch.NewFactory(a.Conn())
|
||||||
ok, err := a.isValidNS(ns)
|
ok, err := a.isValidNS(ns)
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@ func (b *Browser) SetInstance(path string) {
|
||||||
// Start initializes browser updates.
|
// Start initializes browser updates.
|
||||||
func (b *Browser) Start() {
|
func (b *Browser) Start() {
|
||||||
b.app.Config.ValidateFavorites()
|
b.app.Config.ValidateFavorites()
|
||||||
|
if err := b.app.switchNS(b.GetModel().GetNamespace()); err != nil {
|
||||||
|
log.Error().Err(err).Msgf("ns switch failed")
|
||||||
|
}
|
||||||
if err := b.app.Config.Save(); err != nil {
|
if err := b.app.Config.Save(); err != nil {
|
||||||
log.Error().Err(err).Msgf("Config Save")
|
log.Error().Err(err).Msgf("Config Save")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,11 +155,8 @@ func (c *Command) defaultCmd() error {
|
||||||
}
|
}
|
||||||
tokens := strings.Split(view, " ")
|
tokens := strings.Split(view, " ")
|
||||||
cmd := view
|
cmd := view
|
||||||
if len(tokens) == 1 || c.app.Conn().Config().OverrideNS {
|
if len(tokens) == 1 {
|
||||||
ns, err := c.app.Conn().Config().CurrentNamespaceName()
|
cmd = tokens[0] + " " + c.app.Config.ActiveNamespace()
|
||||||
if err == nil && !isContextCmd(tokens[0]) {
|
|
||||||
cmd = tokens[0] + " " + ns
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.run(cmd, "", true); err != nil {
|
if err := c.run(cmd, "", true); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,7 @@ func pipe(ctx context.Context, opts shellOpts, cmds ...*exec.Cmd) error {
|
||||||
return cmd.Start()
|
return cmd.Start()
|
||||||
}
|
}
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
// BOZO!!
|
||||||
//cmd.SysProcAttr = &syscall.SysProcAttr{
|
//cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
//// //Setpgid: true,
|
//// //Setpgid: true,
|
||||||
//// //Setctty: true,
|
//// //Setctty: true,
|
||||||
|
|
@ -385,6 +386,8 @@ func pipe(ctx context.Context, opts shellOpts, cmds ...*exec.Cmd) error {
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
log.Debug().Msgf("Running Done")
|
log.Debug().Msgf("Running Done")
|
||||||
return err
|
return err
|
||||||
|
|
||||||
|
// BOZO!!
|
||||||
// select {
|
// select {
|
||||||
// case <-ctx.Done():
|
// case <-ctx.Done():
|
||||||
// return errors.New("canceled by operator")
|
// return errors.New("canceled by operator")
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,9 @@ package view
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
|
||||||
"github.com/derailed/k9s/internal/port"
|
"github.com/derailed/k9s/internal/port"
|
||||||
"github.com/derailed/k9s/internal/render"
|
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
@ -47,13 +44,13 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
|
||||||
coField.SetPlaceholder("Enter a container name/port")
|
coField.SetPlaceholder("Enter a container name/port")
|
||||||
}
|
}
|
||||||
f.AddInputField("Local Port:", p2, fieldLen, nil, nil)
|
f.AddInputField("Local Port:", p2, fieldLen, nil, nil)
|
||||||
poField := f.GetFormItemByLabel("Local Port:").(*tview.InputField)
|
loField := f.GetFormItemByLabel("Local Port:").(*tview.InputField)
|
||||||
if poField.GetText() == "" {
|
if loField.GetText() == "" {
|
||||||
poField.SetPlaceholder("Enter a local port")
|
loField.SetPlaceholder("Enter a local port")
|
||||||
}
|
}
|
||||||
coField.SetChangedFunc(func(s string) {
|
coField.SetChangedFunc(func(s string) {
|
||||||
port := extractPort(s)
|
port := extractPort(s)
|
||||||
poField.SetText(port)
|
loField.SetText(port)
|
||||||
p2 = port
|
p2 = port
|
||||||
})
|
})
|
||||||
f.AddInputField("Address:", address, fieldLen, nil, func(h string) {
|
f.AddInputField("Address:", address, fieldLen, nil, func(h string) {
|
||||||
|
|
@ -69,11 +66,15 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe
|
||||||
}
|
}
|
||||||
|
|
||||||
f.AddButton("OK", func() {
|
f.AddButton("OK", func() {
|
||||||
if coField.GetText() == "" || poField.GetText() == "" {
|
if coField.GetText() == "" || loField.GetText() == "" {
|
||||||
v.App().Flash().Err(fmt.Errorf("container to local port mismatch"))
|
v.App().Flash().Err(fmt.Errorf("container to local port mismatch"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tt, err := port.ToTunnels(address, coField.GetText(), poField.GetText())
|
if !ports.MatchSpec(coField.GetText()) {
|
||||||
|
v.App().Flash().Err(fmt.Errorf("invalid container port"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tt, err := port.ToTunnels(address, coField.GetText(), loField.GetText())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.App().Flash().Err(err)
|
v.App().Flash().Err(err)
|
||||||
return
|
return
|
||||||
|
|
@ -121,26 +122,11 @@ func DismissPortForwards(v ResourceViewer, p *ui.Pages) {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func extractPort(p string) string {
|
func extractPort(coPort string) string {
|
||||||
rx := regexp.MustCompile(`\A([\w|-]+)/?([\w|-]+)?:?(\d+)?(╱UDP)?\z`)
|
tokens := strings.Split(coPort, "::")
|
||||||
mm := rx.FindStringSubmatch(p)
|
if len(tokens) < 2 {
|
||||||
if len(mm) != 5 {
|
return ""
|
||||||
return p
|
|
||||||
}
|
|
||||||
for i := 3; i > 0; i-- {
|
|
||||||
if mm[i] != "" {
|
|
||||||
return mm[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractContainer(p string) string {
|
|
||||||
tokens := strings.Split(p, ":")
|
|
||||||
if len(tokens) != 2 {
|
|
||||||
return render.NAValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
co, _ := client.Namespaced(tokens[0])
|
return tokens[1]
|
||||||
return co
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,23 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractContainer(t *testing.T) {
|
func TestExtractPort(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
port, e string
|
portSpec, e string
|
||||||
}{
|
}{
|
||||||
"full": {
|
"full": {
|
||||||
"co/port:8000", "co",
|
portSpec: "co::8000",
|
||||||
|
e: "8000",
|
||||||
},
|
},
|
||||||
"unamed": {
|
"toast": {
|
||||||
"co/:8000", "co",
|
portSpec: "co:8000",
|
||||||
},
|
|
||||||
"protocol": {
|
|
||||||
"co/dns:53╱UDP", "co",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, extractContainer(u.port))
|
assert.Equal(t, u.e, extractPort(u.portSpec))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ func (r *RestartExtender) bindKeys(aa ui.KeyActions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aa.Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
// BOZO!!
|
|
||||||
ui.KeyR: ui.NewKeyAction("Restart", r.restartCmd, true),
|
ui.KeyR: ui.NewKeyAction("Restart", r.restartCmd, true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue