add pf indicator
parent
766bf133da
commit
f1111174aa
|
|
@ -0,0 +1,34 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.20.6
|
||||
|
||||
## 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 is as ever very much noticed and appreciated!
|
||||
|
||||
Also if you dig this tool, consider joining our [sponsorhip 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)
|
||||
|
||||
---
|
||||
|
||||
## First A Word From Our Sponsors...
|
||||
|
||||
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
|
||||
|
||||
* [Remo Eichenberger](https://github.com/remoe)
|
||||
* [Ken Ahrens](https://github.com/kenahrens)
|
||||
|
||||
Maintenance Release!
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
* [Issue #778](https://github.com/derailed/k9s/issues/778)
|
||||
* [Issue #774](https://github.com/derailed/k9s/issues/774)
|
||||
* [Issue #761](https://github.com/derailed/k9s/issues/761)
|
||||
* [Issue #759](https://github.com/derailed/k9s/issues/759)
|
||||
* [Issue #756](https://github.com/derailed/k9s/issues/756)
|
||||
|
||||
---
|
||||
|
||||
<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)
|
||||
|
|
@ -24,14 +24,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
cacheSize = 100
|
||||
cacheExpiry = 5 * time.Minute
|
||||
cacheMXKey = "metrics"
|
||||
cacheMXAPIKey = "metricsAPI"
|
||||
checkConnTimeout = 3 * time.Second
|
||||
|
||||
// CallTimeout represents default api call timeout.
|
||||
CallTimeout = 5 * time.Second
|
||||
cacheSize = 100
|
||||
cacheExpiry = 5 * time.Minute
|
||||
cacheMXKey = "metrics"
|
||||
cacheMXAPIKey = "metricsAPI"
|
||||
)
|
||||
|
||||
var supportedMetricsAPIVersions = []string{"v1beta1"}
|
||||
|
|
@ -157,7 +153,7 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
|
|||
}
|
||||
client, sar := dial.AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout())
|
||||
defer cancel()
|
||||
for _, v := range verbs {
|
||||
sar.Spec.ResourceAttributes.Verb = v
|
||||
|
|
@ -203,7 +199,7 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout())
|
||||
defer cancel()
|
||||
nn, err := dial.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
|
|
@ -235,7 +231,6 @@ func (a *APIClient) CheckConnectivity() bool {
|
|||
a.connOK = false
|
||||
return a.connOK
|
||||
}
|
||||
cfg.Timeout = checkConnTimeout
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Unable to connect to api server")
|
||||
|
|
@ -280,7 +275,13 @@ func (a *APIClient) HasMetrics() bool {
|
|||
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
||||
return flag
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||
|
||||
timeout, err := time.ParseDuration(*a.config.flags.Timeout)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("parsing duration")
|
||||
return false
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
if _, err := dial.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{Limit: 1}); err == nil {
|
||||
flag = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
|
@ -14,8 +15,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultQPS = 50
|
||||
defaultBurst = 50
|
||||
defaultQPS = 50
|
||||
defaultBurst = 50
|
||||
defaultCallTimeoutDuration time.Duration = 5 * time.Second
|
||||
defaultCallTimeout = "5s"
|
||||
)
|
||||
|
||||
// Config tracks a kubernetes configuration.
|
||||
|
|
@ -29,12 +32,34 @@ type Config struct {
|
|||
|
||||
// NewConfig returns a new k8s config or an error if the flags are invalid.
|
||||
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
|
||||
timeout := defaultCallTimeout
|
||||
if f.Timeout == nil {
|
||||
f.Timeout = &timeout
|
||||
} else {
|
||||
_, err := time.ParseDuration(*f.Timeout)
|
||||
if err != nil {
|
||||
f.Timeout = &timeout
|
||||
}
|
||||
}
|
||||
return &Config{
|
||||
flags: f,
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// CallTimeout returns the call timeout if set or the default if not set.
|
||||
func (c *Config) CallTimeout() time.Duration {
|
||||
if c.flags.Timeout == nil {
|
||||
return defaultCallTimeoutDuration
|
||||
}
|
||||
dur, err := time.ParseDuration(*c.flags.Timeout)
|
||||
if err != nil {
|
||||
return defaultCallTimeoutDuration
|
||||
}
|
||||
|
||||
return dur
|
||||
}
|
||||
|
||||
// Flags returns configuration flags.
|
||||
func (c *Config) Flags() *genericclioptions.ConfigFlags {
|
||||
return c.flags
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ func (a *Aliases) loadDefaultAliases() {
|
|||
a.declare("users", "user", "usr")
|
||||
a.declare("groups", "group", "grp")
|
||||
a.declare("portforwards", "portforward", "pf")
|
||||
a.declare("benchmarks", "benchmark", "be")
|
||||
a.declare("benchmarks", "bench", "benchmark", "be")
|
||||
a.declare("screendumps", "screendump", "sd")
|
||||
a.declare("pulses", "pulse", "pu", "hz")
|
||||
a.declare("xrays", "xray", "x")
|
||||
|
|
|
|||
|
|
@ -215,9 +215,9 @@ func newStyle() Style {
|
|||
|
||||
func newCharts() Charts {
|
||||
return Charts{
|
||||
BgColor: "default",
|
||||
DialBgColor: "default",
|
||||
ChartBgColor: "default",
|
||||
BgColor: "black",
|
||||
DialBgColor: "black",
|
||||
ChartBgColor: "black",
|
||||
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
|
||||
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
|
||||
ResourceColors: map[string]Colors{
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -38,16 +40,23 @@ func (b *Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
if !ok {
|
||||
return nil, errors.New("no benchmark dir found in context")
|
||||
}
|
||||
path, _ := ctx.Value(internal.KeyPath).(string)
|
||||
|
||||
ff, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oo := make([]runtime.Object, len(ff))
|
||||
for i, f := range ff {
|
||||
oo[i] = render.BenchInfo{File: f, Path: filepath.Join(dir, f.Name())}
|
||||
oo := make([]runtime.Object, 0, len(ff))
|
||||
for _, f := range ff {
|
||||
log.Debug().Msgf("BENCH-LIST %q::%q", strings.Replace(path, "/", "_", 1), f.Name())
|
||||
if path != "" && !strings.HasPrefix(f.Name(), strings.Replace(path, "/", "_", 1)) {
|
||||
log.Debug().Msgf(" SKIP...")
|
||||
continue
|
||||
}
|
||||
oo = append(oo, render.BenchInfo{File: f, Path: filepath.Join(dir, f.Name())})
|
||||
}
|
||||
log.Debug().Msgf("BENCH-FILES %#v", oo)
|
||||
|
||||
return oo, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,26 +39,19 @@ func (c *CronJob) Run(path string) error {
|
|||
return fmt.Errorf("user is not authorize to run cronjobs")
|
||||
}
|
||||
|
||||
// BOZO!! Factory resource??
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
o, err := c.Factory.Get("batch/v1beta1/cronjobs", path, true, labels.Everything())
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var cj batchv1beta1.CronJob
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
|
||||
if err != nil {
|
||||
return errors.New("expecting CronJob resource")
|
||||
}
|
||||
|
||||
var jobName = cj.Name
|
||||
if len(cj.Name) >= maxJobNameSize {
|
||||
jobName = cj.Name[0:maxJobNameSize]
|
||||
}
|
||||
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jobName + "-manual-" + rand.String(3),
|
||||
|
|
@ -71,6 +64,8 @@ func (c *CronJob) Run(path string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.Client().Config().CallTimeout())
|
||||
defer cancel()
|
||||
_, err = dial.BatchV1().Jobs(ns).Create(ctx, job, metav1.CreateOptions{})
|
||||
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -118,14 +118,15 @@ func (g *Generic) Delete(path string, cascade, force bool) error {
|
|||
PropagationPolicy: &p,
|
||||
GracePeriodSeconds: grace,
|
||||
}
|
||||
// BOZO!! Move to caller!
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
|
||||
dial, err := g.dynClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// BOZO!! Move to caller!
|
||||
ctx, cancel := context.WithTimeout(context.Background(), g.Client().Config().CallTimeout())
|
||||
defer cancel()
|
||||
|
||||
if client.IsClusterScoped(ns) {
|
||||
return dial.Delete(ctx, n, opts)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
|||
for _, co := range po.Spec.InitContainers {
|
||||
log.Debug().Msgf("Tailing INIT-CO %q", co.Name)
|
||||
opts.Container = co.Name
|
||||
if err := p.TailLogs(ctx, c, opts); err != nil {
|
||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
tailed = true
|
||||
|
|
|
|||
|
|
@ -40,21 +40,25 @@ func (p *PortForward) Delete(path string, cascade, force bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// List returns a collection of screen dumps.
|
||||
// List returns a collection of port forwards.
|
||||
func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||
benchFile, ok := ctx.Value(internal.KeyBenchCfg).(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no bench file found in context")
|
||||
}
|
||||
path, _ := ctx.Value(internal.KeyPath).(string)
|
||||
|
||||
config, err := config.NewBench(benchFile)
|
||||
if err != nil {
|
||||
log.Debug().Msgf("No custom benchmark config file found")
|
||||
}
|
||||
|
||||
cc := config.Benchmarks.Containers
|
||||
oo := make([]runtime.Object, 0, len(p.Factory.Forwarders()))
|
||||
for k, f := range p.Factory.Forwarders() {
|
||||
ff, cc := p.Factory.Forwarders(), config.Benchmarks.Containers
|
||||
oo := make([]runtime.Object, 0, len(ff))
|
||||
for k, f := range ff {
|
||||
if !strings.HasPrefix(k, path) {
|
||||
continue
|
||||
}
|
||||
cfg := render.BenchCfg{
|
||||
C: config.Benchmarks.Defaults.C,
|
||||
N: config.Benchmarks.Defaults.N,
|
||||
|
|
|
|||
|
|
@ -103,8 +103,14 @@ func (p *PortForwarder) HasPortMapping(m string) bool {
|
|||
}
|
||||
|
||||
// Start initiates a port forward session for a given pod and ports.
|
||||
func (p *PortForwarder) Start(path, co string, t client.PortTunnel) (*portforward.PortForwarder, error) {
|
||||
fwds := []string{t.PortMap()}
|
||||
func (p *PortForwarder) Start(path, co string, tt []client.PortTunnel) (*portforward.PortForwarder, error) {
|
||||
if len(tt) == 0 {
|
||||
return nil, fmt.Errorf("no ports assigned")
|
||||
}
|
||||
fwds := make([]string, 0, len(tt))
|
||||
for _, t := range tt {
|
||||
fwds = append(fwds, t.PortMap())
|
||||
}
|
||||
p.path, p.container, p.ports, p.age = path, co, fwds, time.Now()
|
||||
|
||||
ns, n := client.Namespaced(path)
|
||||
|
|
@ -152,7 +158,7 @@ func (p *PortForwarder) Start(path, co string, t client.PortTunnel) (*portforwar
|
|||
Name(n).
|
||||
SubResource("portforward")
|
||||
|
||||
return p.forwardPorts("POST", req.URL(), t.Address, fwds)
|
||||
return p.forwardPorts("POST", req.URL(), tt[0].Address, fwds)
|
||||
}
|
||||
|
||||
func (p *PortForwarder) forwardPorts(method string, url *url.URL, address string, ports []string) (*portforward.PortForwarder, error) {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ func (c *ClusterInfo) Refresh() {
|
|||
data.K9sVer = c.version
|
||||
data.K8sVer = c.cluster.Version()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.cluster.factory.Client().Config().CallTimeout())
|
||||
defer cancel()
|
||||
var mx client.ClusterMetrics
|
||||
if err := c.cluster.Metrics(ctx, &mx); err == nil {
|
||||
|
|
|
|||
|
|
@ -83,15 +83,16 @@ func (c *CmdBuff) Delete() {
|
|||
}
|
||||
|
||||
// ClearText clears out command buffer.
|
||||
func (c *CmdBuff) ClearText() {
|
||||
func (c *CmdBuff) ClearText(fire bool) {
|
||||
c.buff = make([]rune, 0, maxBuff)
|
||||
c.fireBufferChanged()
|
||||
if fire {
|
||||
c.fireBufferChanged()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset clears out the command buffer and deactivates it.
|
||||
func (c *CmdBuff) Reset() {
|
||||
c.ClearText()
|
||||
c.fireBufferChanged()
|
||||
c.ClearText(true)
|
||||
c.SetActive(false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func TestCmdBuffChanged(t *testing.T) {
|
|||
assert.Equal(t, "", b.GetText())
|
||||
|
||||
b.Add('c')
|
||||
b.ClearText()
|
||||
b.ClearText(true)
|
||||
assert.Equal(t, 0, l.act)
|
||||
assert.Equal(t, 0, l.inact)
|
||||
assert.Equal(t, "", l.text)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestTableReconcile(t *testing.T) {
|
|||
err := ta.reconcile(ctx)
|
||||
assert.Nil(t, err)
|
||||
data := ta.Peek()
|
||||
assert.Equal(t, 17, len(data.Header))
|
||||
assert.Equal(t, 18, len(data.Header))
|
||||
assert.Equal(t, 1, len(data.RowEvents))
|
||||
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ func TestTableHydrate(t *testing.T) {
|
|||
|
||||
assert.Nil(t, hydrate("blee", oo, rr, render.Pod{}))
|
||||
assert.Equal(t, 1, len(rr))
|
||||
assert.Equal(t, 17, len(rr[0].Fields))
|
||||
assert.Equal(t, 18, len(rr[0].Fields))
|
||||
}
|
||||
|
||||
func TestTableGenericHydrate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestTableRefresh(t *testing.T) {
|
|||
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
|
||||
ta.Refresh(ctx)
|
||||
data := ta.Peek()
|
||||
assert.Equal(t, 17, len(data.Header))
|
||||
assert.Equal(t, 18, len(data.Header))
|
||||
assert.Equal(t, 1, len(data.RowEvents))
|
||||
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
||||
assert.Equal(t, 1, l.count)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ func NewBenchmark(base, version string, cfg config.BenchConfig) (*Benchmark, err
|
|||
}
|
||||
|
||||
func (b *Benchmark) init(base, version string) error {
|
||||
log.Debug().Msgf("BENCH-INIT")
|
||||
req, err := http.NewRequest(b.config.HTTP.Method, base, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -111,7 +110,6 @@ func (b *Benchmark) Canceled() bool {
|
|||
// Run starts a benchmark,
|
||||
func (b *Benchmark) Run(cluster string, done func()) {
|
||||
log.Debug().Msgf("Running benchmark on cluster %s", cluster)
|
||||
log.Debug().Msgf("BENCH-CFG %#v", b.worker)
|
||||
buff := new(bytes.Buffer)
|
||||
b.worker.Writer = buff
|
||||
// this call will block until the benchmark is complete or timesout.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type Alias struct{}
|
|||
// ColorerFunc colors a resource row.
|
||||
func (Alias) ColorerFunc() ColorerFunc {
|
||||
return func(ns string, _ Header, re RowEvent) tcell.Color {
|
||||
return tcell.ColorMediumSpringGreen
|
||||
return tcell.ColorAliceBlue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@ func TestAliasColorer(t *testing.T) {
|
|||
"addAll": {
|
||||
ns: client.AllNamespaces,
|
||||
re: render.RowEvent{Kind: render.EventAdd, Row: r},
|
||||
e: tcell.ColorMediumSpringGreen},
|
||||
e: tcell.ColorAliceBlue},
|
||||
"deleteAll": {
|
||||
ns: client.AllNamespaces,
|
||||
re: render.RowEvent{Kind: render.EventDelete, Row: r},
|
||||
e: tcell.ColorMediumSpringGreen},
|
||||
e: tcell.ColorAliceBlue},
|
||||
"updateAll": {
|
||||
ns: client.AllNamespaces,
|
||||
re: render.RowEvent{Kind: render.EventUpdate, Row: r},
|
||||
e: tcell.ColorMediumSpringGreen,
|
||||
e: tcell.ColorAliceBlue,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func (Benchmark) Header(ns string) Header {
|
|||
func (b Benchmark) Render(o interface{}, ns string, r *Row) error {
|
||||
bench, ok := o.(BenchInfo)
|
||||
if !ok {
|
||||
return fmt.Errorf("expecting benchinfo but got `%T", o)
|
||||
return fmt.Errorf("No benchmarks available %T", o)
|
||||
}
|
||||
|
||||
data, err := b.readFile(bench.Path)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ func (c Container) ColorerFunc() ColorerFunc {
|
|||
func (Container) Header(ns string) Header {
|
||||
return Header{
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "PF"},
|
||||
HeaderColumn{Name: "IMAGE"},
|
||||
HeaderColumn{Name: "READY"},
|
||||
HeaderColumn{Name: "STATE"},
|
||||
|
|
@ -103,6 +104,7 @@ func (c Container) Render(o interface{}, name string, r *Row) error {
|
|||
r.ID = co.Container.Name
|
||||
r.Fields = Fields{
|
||||
co.Container.Name,
|
||||
"●",
|
||||
co.Container.Image,
|
||||
ready,
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ func TestContainer(t *testing.T) {
|
|||
assert.Equal(t, "fred", r.ID)
|
||||
assert.Equal(t, render.Fields{
|
||||
"fred",
|
||||
"●",
|
||||
"img",
|
||||
"false",
|
||||
"Running",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type Generic struct {
|
|||
ageIndex int
|
||||
}
|
||||
|
||||
// Happy returns true if resoure is happy, false otherwise
|
||||
// Happy returns true if resource is happy, false otherwise
|
||||
func (Generic) Happy(ns string, r Row) bool {
|
||||
return true
|
||||
}
|
||||
|
|
@ -79,6 +79,10 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error {
|
|||
ageCell = c
|
||||
continue
|
||||
}
|
||||
if c == nil {
|
||||
r.Fields = append(r.Fields, Blank)
|
||||
continue
|
||||
}
|
||||
r.Fields = append(r.Fields, fmt.Sprintf("%v", c))
|
||||
}
|
||||
if ageCell != nil {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ func (Pod) Header(ns string) Header {
|
|||
return Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "PF"},
|
||||
HeaderColumn{Name: "READY"},
|
||||
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
|
|
@ -97,6 +98,7 @@ func (p Pod) Render(o interface{}, ns string, r *Row) error {
|
|||
r.Fields = Fields{
|
||||
po.Namespace,
|
||||
po.ObjectMeta.Name,
|
||||
"●",
|
||||
strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss)),
|
||||
strconv.Itoa(rc),
|
||||
phase,
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ func TestPodRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/nginx", r.ID)
|
||||
e := render.Fields{"default", "nginx", "1/1", "0", "Running", "10", "10", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:14])
|
||||
e := render.Fields{"default", "nginx", "●", "1/1", "0", "Running", "10", "10", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:15])
|
||||
}
|
||||
|
||||
func BenchmarkPodRender(b *testing.B) {
|
||||
|
|
@ -190,8 +190,8 @@ func TestPodInitRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/nginx", r.ID)
|
||||
e := render.Fields{"default", "nginx", "1/1", "0", "Init:0/1", "10", "10", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:14])
|
||||
e := render.Fields{"default", "nginx", "●", "1/1", "0", "Init:0/1", "10", "10", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:15])
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ func NewTableData() *TableData {
|
|||
return &TableData{}
|
||||
}
|
||||
|
||||
// IndexOfHeader return the index of the header.
|
||||
func (t *TableData) IndexOfHeader(h string) int {
|
||||
return t.Header.IndexOf(h, false)
|
||||
}
|
||||
|
||||
// Labelize prints out specific label columns
|
||||
func (t *TableData) Labelize(labels []string) TableData {
|
||||
labelCol := t.Header.IndexOf("LABELS", true)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ const (
|
|||
|
||||
// Pending represents a pod pending status.
|
||||
Pending = "Pending"
|
||||
|
||||
// Blank represents no value.
|
||||
Blank = ""
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ func (a *App) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if !a.CmdBuff().IsActive() {
|
||||
return evt
|
||||
}
|
||||
a.CmdBuff().ClearText()
|
||||
a.CmdBuff().ClearText(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@ func (a *App) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
a.ResetPrompt(a.cmdModel)
|
||||
a.cmdModel.ClearText()
|
||||
a.cmdModel.ClearText(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ type PromptModel interface {
|
|||
GetText() string
|
||||
|
||||
// ClearText clears out model text.
|
||||
ClearText()
|
||||
ClearText(fire bool)
|
||||
|
||||
// Notify notifies all listener of current suggestions.
|
||||
Notify()
|
||||
|
|
@ -135,13 +135,13 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
case tcell.KeyRune:
|
||||
p.model.Add(evt.Rune())
|
||||
case tcell.KeyEscape:
|
||||
p.model.ClearText()
|
||||
p.model.ClearText(true)
|
||||
p.model.SetActive(false)
|
||||
case tcell.KeyEnter, tcell.KeyCtrlE:
|
||||
p.model.SetText(p.model.GetText())
|
||||
p.model.SetActive(false)
|
||||
case tcell.KeyCtrlW, tcell.KeyCtrlU:
|
||||
p.model.ClearText()
|
||||
p.model.ClearText(true)
|
||||
case tcell.KeyUp:
|
||||
if s, ok := m.NextSuggestion(); ok {
|
||||
p.suggest(p.model.GetText(), s)
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ func (t *Table) filtered(data render.TableData) render.TableData {
|
|||
filtered, err := rxFilter(t.cmdBuff.GetText(), filtered)
|
||||
if err != nil {
|
||||
log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp")
|
||||
t.cmdBuff.ClearText()
|
||||
t.cmdBuff.ClearText(true)
|
||||
}
|
||||
|
||||
return filtered
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ func NewAlias(gvr client.GVR) ResourceViewer {
|
|||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
a.GetTable().SetColorerFn(render.Alias{}.ColorerFunc())
|
||||
a.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||
a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||
a.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
||||
a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
||||
a.SetBindKeysFn(a.bindKeys)
|
||||
a.SetContextFn(a.aliasContext)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ var ExitStatus = ""
|
|||
|
||||
const (
|
||||
splashDelay = 1 * time.Second
|
||||
clusterRefresh = 5 * time.Second
|
||||
clusterRefresh = 15 * time.Second
|
||||
maxConRetry = 15
|
||||
clusterInfoWidth = 50
|
||||
clusterInfoPad = 15
|
||||
|
|
@ -284,6 +284,7 @@ func (a *App) refreshCluster() {
|
|||
} else {
|
||||
a.ClearStatus(true)
|
||||
}
|
||||
a.factory.ValidatePortForwards()
|
||||
} else {
|
||||
atomic.AddInt32(&a.conRetry, 1)
|
||||
if c != nil {
|
||||
|
|
@ -337,7 +338,7 @@ func (a *App) isValidNS(ns string) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), a.Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
dial, err := a.Conn().Dial()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -130,7 +130,9 @@ func (b *Browser) Start() {
|
|||
|
||||
// Stop terminates browser updates.
|
||||
func (b *Browser) Stop() {
|
||||
log.Debug().Msgf("BRO-STOP %v", b.GVR())
|
||||
if b.cancelFn != nil {
|
||||
log.Debug().Msgf("Canceling!!")
|
||||
b.cancelFn()
|
||||
b.cancelFn = nil
|
||||
}
|
||||
|
|
@ -153,7 +155,7 @@ func (b *Browser) BufferActive(state bool, k model.BufferKind) {
|
|||
if state {
|
||||
return
|
||||
}
|
||||
b.GetModel().Refresh(b.defaultContext())
|
||||
b.GetModel().Refresh(b.prepareContext())
|
||||
b.app.QueueUpdateDraw(func() {
|
||||
b.Update(b.GetModel().Peek())
|
||||
if b.GetRowCount() > 1 {
|
||||
|
|
@ -242,7 +244,7 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
func (b *Browser) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !b.CmdBuff().InCmdMode() {
|
||||
b.CmdBuff().Reset()
|
||||
b.CmdBuff().ClearText(false)
|
||||
return b.App().PrevCmd(evt)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
|
|
@ -28,10 +30,24 @@ func NewContainer(gvr client.GVR) ResourceViewer {
|
|||
c.GetTable().SetEnterFn(c.viewLogs)
|
||||
c.GetTable().SetColorerFn(render.Container{}.ColorerFunc())
|
||||
c.SetBindKeysFn(c.bindKeys)
|
||||
c.GetTable().SetDecorateFn(c.portForwardIndicator)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *Container) portForwardIndicator(data render.TableData) render.TableData {
|
||||
ff := c.App().factory.Forwarders()
|
||||
|
||||
col := data.IndexOfHeader("PF")
|
||||
for _, re := range data.RowEvents {
|
||||
if ff.IsContainerForwarded(c.GetTable().Path, re.Row.ID) {
|
||||
re.Row.Fields[col] = "[orange::b]Ⓕ"
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Name returns the component name.
|
||||
func (c *Container) Name() string { return containerTitle }
|
||||
|
||||
|
|
@ -50,6 +66,7 @@ func (c *Container) bindKeys(aa ui.KeyActions) {
|
|||
}
|
||||
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyF: ui.NewKeyAction("Show PortForward", c.showPFCmd, true),
|
||||
ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true),
|
||||
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", c.GetTable().SortColCmd("RESTARTS", false), false),
|
||||
})
|
||||
|
|
@ -60,7 +77,7 @@ func (c *Container) k9sEnv() Env {
|
|||
path := c.GetTable().GetSelectedItem()
|
||||
row, ok := c.GetTable().GetSelectedRow(path)
|
||||
if !ok {
|
||||
log.Error().Msgf("unable to locate seleted row for %q", path)
|
||||
log.Error().Msgf("unable to locate selected row for %q", path)
|
||||
}
|
||||
env := defaultEnv(c.App().Conn().Config(), path, c.GetTable().GetModel().Peek().Header, row)
|
||||
env["NAMESPACE"], env["POD"] = client.Namespaced(c.GetTable().Path)
|
||||
|
|
@ -79,6 +96,30 @@ func (c *Container) viewLogs(app *App, model ui.Tabular, gvr, path string) {
|
|||
|
||||
// Handlers...
|
||||
|
||||
func (c *Container) showPFCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := c.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
if !c.App().factory.Forwarders().IsContainerForwarded(c.GetTable().Path, path) {
|
||||
c.App().Flash().Errf("no portforwards defined")
|
||||
return nil
|
||||
}
|
||||
pf := NewPortForward(client.NewGVR("portforwards"))
|
||||
pf.SetContextFn(c.portForwardContext)
|
||||
if err := c.App().inject(pf); err != nil {
|
||||
c.App().Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) portForwardContext(ctx context.Context) context.Context {
|
||||
ctx = context.WithValue(ctx, internal.KeyBenchCfg, c.App().BenchFile)
|
||||
return context.WithValue(ctx, internal.KeyPath, c.GetTable().Path)
|
||||
}
|
||||
|
||||
func (c *Container) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
sel := c.GetTable().GetSelectedItem()
|
||||
if sel == "" {
|
||||
|
|
@ -151,7 +192,7 @@ func (c *Container) isForwardable(path string) ([]string, bool) {
|
|||
}
|
||||
}
|
||||
if cs == nil {
|
||||
log.Error().Err(fmt.Errorf("unable to locate container status named %q", path))
|
||||
log.Error().Err(fmt.Errorf("unable to locate container status for %q", path))
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ func TestContainerNew(t *testing.T) {
|
|||
|
||||
assert.Nil(t, c.Init(makeCtx()))
|
||||
assert.Equal(t, "Containers", c.Name())
|
||||
assert.Equal(t, 17, len(c.Hints()))
|
||||
assert.Equal(t, 18, len(c.Hints()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func TestHelp(t *testing.T) {
|
|||
v := view.NewHelp()
|
||||
|
||||
assert.Nil(t, v.Init(ctx))
|
||||
assert.Equal(t, 23, v.GetRowCount())
|
||||
assert.Equal(t, 24, v.GetRowCount())
|
||||
assert.Equal(t, 8, v.GetColumnCount())
|
||||
assert.Equal(t, "<a>", strings.TrimSpace(v.GetCell(1, 0).Text))
|
||||
assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).Text))
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ func (n *Node) yamlCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), n.App().Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
|
||||
sel := n.GetTable().GetSelectedItem()
|
||||
|
|
|
|||
|
|
@ -56,13 +56,25 @@ func (p *PortForward) bindKeys(aa ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if err := p.App().inject(NewBenchmark(client.NewGVR("benchmarks"))); err != nil {
|
||||
b := NewBenchmark(client.NewGVR("benchmarks"))
|
||||
b.SetContextFn(p.getContext)
|
||||
if err := p.App().inject(b); err != nil {
|
||||
p.App().Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortForward) getContext(ctx context.Context) context.Context {
|
||||
ctx = context.WithValue(ctx, internal.KeyDir, benchDir(p.App().Config))
|
||||
path := p.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return ctx
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, internal.KeyPath, path)
|
||||
}
|
||||
|
||||
func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if p.bench != nil {
|
||||
p.App().Status(model.FlashErr, "Benchmark Canceled!")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
const portForwardKey = "portforward"
|
||||
|
||||
// PortForwardCB represents a port-forward callback function.
|
||||
type PortForwardCB func(v ResourceViewer, path, co string, mapper client.PortTunnel)
|
||||
type PortForwardCB func(v ResourceViewer, path, co string, mapper []client.PortTunnel)
|
||||
|
||||
// ShowPortForwards pops a port forwarding configuration dialog.
|
||||
func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortForwardCB) {
|
||||
|
|
@ -42,12 +42,29 @@ func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortFo
|
|||
pages := v.App().Content.Pages
|
||||
|
||||
f.AddButton("OK", func() {
|
||||
tunnel := client.PortTunnel{
|
||||
Address: address,
|
||||
LocalPort: p2,
|
||||
ContainerPort: extractPort(p1),
|
||||
pp1 := strings.Split(p1, ",")
|
||||
pp2 := strings.Split(p2, ",")
|
||||
if len(pp1) == 0 || len(pp1) != len(pp2) {
|
||||
v.App().Flash().Err(fmt.Errorf("container to local port mismatch"))
|
||||
return
|
||||
}
|
||||
okFn(v, path, extractContainer(p1), tunnel)
|
||||
|
||||
for _, p := range pp1 {
|
||||
if !hasPort(p, ports) {
|
||||
v.App().Flash().Err(fmt.Errorf("container port must match exposed ports"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var tt []client.PortTunnel
|
||||
for i := range pp1 {
|
||||
tt = append(tt, client.PortTunnel{
|
||||
Address: address,
|
||||
LocalPort: pp2[i],
|
||||
ContainerPort: extractPort(pp1[i]),
|
||||
})
|
||||
}
|
||||
okFn(v, path, extractContainer(pp1[0]), tt)
|
||||
})
|
||||
f.AddButton("Cancel", func() {
|
||||
DismissPortForwards(v, pages)
|
||||
|
|
@ -73,6 +90,16 @@ func DismissPortForwards(v ResourceViewer, p *ui.Pages) {
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func hasPort(port string, pp []string) bool {
|
||||
for _, p := range pp {
|
||||
if p != port {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func extractPort(p string) string {
|
||||
rx := regexp.MustCompile(`\A([\w|-]+)/?([\w|-]+)?:?(\d+)?(╱UDP)?\z`)
|
||||
mm := rx.FindStringSubmatch(p)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
p.App().Flash().Err(err)
|
||||
return nil
|
||||
}
|
||||
if p.App().factory.Forwarders().IsPodForwarded(pod) {
|
||||
p.App().Flash().Errf("A PortForward already exist for pod %s", pod)
|
||||
return nil
|
||||
}
|
||||
if err := showFwdDialog(p, pod, startFwdCB); err != nil {
|
||||
p.App().Flash().Err(err)
|
||||
}
|
||||
|
|
@ -100,11 +104,13 @@ func runForward(v ResourceViewer, pf watch.Forwarder, f *portforward.PortForward
|
|||
})
|
||||
}
|
||||
|
||||
func startFwdCB(v ResourceViewer, path, co string, t client.PortTunnel) {
|
||||
err := tryListenPort(t.Address, t.LocalPort)
|
||||
if err != nil {
|
||||
v.App().Flash().Err(err)
|
||||
return
|
||||
func startFwdCB(v ResourceViewer, path, co string, tt []client.PortTunnel) {
|
||||
for _, t := range tt {
|
||||
err := tryListenPort(t.Address, t.LocalPort)
|
||||
if err != nil {
|
||||
v.App().Flash().Err(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := v.App().factory.ForwarderFor(dao.PortForwardID(path, co)); ok {
|
||||
|
|
@ -113,13 +119,13 @@ func startFwdCB(v ResourceViewer, path, co string, t client.PortTunnel) {
|
|||
}
|
||||
|
||||
pf := dao.NewPortForwarder(v.App().factory)
|
||||
fwd, err := pf.Start(path, co, t)
|
||||
fwd, err := pf.Start(path, co, tt)
|
||||
if err != nil {
|
||||
v.App().Flash().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().Msgf(">>> Starting port forward %q %#v", path, t)
|
||||
log.Debug().Msgf(">>> Starting port forward %q %#v", path, tt)
|
||||
go runForward(v, pf, fwd)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,24 @@ func NewPod(gvr client.GVR) ResourceViewer {
|
|||
p.SetBindKeysFn(p.bindKeys)
|
||||
p.GetTable().SetEnterFn(p.showContainers)
|
||||
p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc())
|
||||
p.GetTable().SetDecorateFn(p.portForwardIndicator)
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *Pod) portForwardIndicator(data render.TableData) render.TableData {
|
||||
ff := p.App().factory.Forwarders()
|
||||
|
||||
col := data.IndexOfHeader("PF")
|
||||
for _, re := range data.RowEvents {
|
||||
if ff.IsPodForwarded(re.Row.ID) {
|
||||
re.Row.Fields[col] = "[orange::b]Ⓕ"
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (p *Pod) bindDangerousKeys(aa ui.KeyActions) {
|
||||
aa.Add(ui.KeyActions{
|
||||
tcell.KeyCtrlK: ui.NewKeyAction("Kill", p.killCmd, true),
|
||||
|
|
@ -52,6 +66,7 @@ func (p *Pod) bindKeys(aa ui.KeyActions) {
|
|||
}
|
||||
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true),
|
||||
ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false),
|
||||
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd("RESTARTS", false), false),
|
||||
ui.KeyShiftS: ui.NewKeyAction("Sort Status", p.GetTable().SortColCmd(statusCol, true), false),
|
||||
|
|
@ -90,7 +105,31 @@ func (p *Pod) coContext(ctx context.Context) context.Context {
|
|||
return context.WithValue(ctx, internal.KeyPath, p.GetTable().GetSelectedItem())
|
||||
}
|
||||
|
||||
// Commands...
|
||||
// Handlers...
|
||||
|
||||
func (p *Pod) showPFCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := p.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
if !p.App().factory.Forwarders().IsPodForwarded(path) {
|
||||
p.App().Flash().Errf("no portforwards defined")
|
||||
return nil
|
||||
}
|
||||
pf := NewPortForward(client.NewGVR("portforwards"))
|
||||
pf.SetContextFn(p.portForwardContext)
|
||||
if err := p.App().inject(pf); err != nil {
|
||||
p.App().Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pod) portForwardContext(ctx context.Context) context.Context {
|
||||
ctx = context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile)
|
||||
return context.WithValue(ctx, internal.KeyPath, p.GetTable().GetSelectedItem())
|
||||
}
|
||||
|
||||
func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
sels := p.GetTable().GetSelectedItems()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func TestPodNew(t *testing.T) {
|
|||
|
||||
assert.Nil(t, po.Init(makeCtx()))
|
||||
assert.Equal(t, "Pods", po.Name())
|
||||
assert.Equal(t, 22, len(po.Hints()))
|
||||
assert.Equal(t, 23, len(po.Hints()))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
|
|
@ -45,7 +44,7 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
msg = fmt.Sprintf("Restart %d deployments?", len(paths))
|
||||
}
|
||||
dialog.ShowConfirm(r.App().Content.Pages, "Confirm Restart", msg, func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.App().Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
for _, path := range paths {
|
||||
if err := r.restartRollout(ctx, path); err != nil {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
|
|
@ -75,7 +74,7 @@ func (s *ScaleExtender) makeScaleForm(sel string) *tview.Form {
|
|||
s.App().Flash().Err(err)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
if err := s.scale(ctx, sel, count); err != nil {
|
||||
log.Error().Err(err).Msgf("DP %s scaling failed", sel)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package watch
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -243,8 +244,11 @@ func (f *Factory) ensureFactory(ns string) (di.DynamicSharedInformerFactory, err
|
|||
func (f *Factory) AddForwarder(pf Forwarder) {
|
||||
f.mx.Lock()
|
||||
defer f.mx.Unlock()
|
||||
|
||||
f.forwarders[pf.Path()] = pf
|
||||
|
||||
for k, v := range f.forwarders {
|
||||
log.Debug().Msgf("%q -- %#v", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteForwarder deletes portforward for a given container.
|
||||
|
|
@ -266,6 +270,21 @@ func (f *Factory) ForwarderFor(path string) (Forwarder, bool) {
|
|||
f.mx.RLock()
|
||||
defer f.mx.RUnlock()
|
||||
|
||||
for k := range f.forwarders {
|
||||
log.Debug().Msgf("KEY %q::%q", k, path)
|
||||
}
|
||||
fwd, ok := f.forwarders[path]
|
||||
return fwd, ok
|
||||
}
|
||||
|
||||
// Validate check if pods are still around for portforwards.
|
||||
func (f *Factory) ValidatePortForwards() {
|
||||
for k, fwd := range f.forwarders {
|
||||
tokens := strings.Split(k, ":")
|
||||
_, err := f.Get("v1/pods", tokens[0], false, labels.Everything())
|
||||
if err != nil {
|
||||
fwd.Stop()
|
||||
delete(f.forwarders, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
// Forwarder represents a port forwarder.
|
||||
type Forwarder interface {
|
||||
// Start starts a port-forward.
|
||||
Start(path, co string, t client.PortTunnel) (*portforward.PortForwarder, error)
|
||||
Start(path, co string, tt []client.PortTunnel) (*portforward.PortForwarder, error)
|
||||
|
||||
// Stop terminates a port forward.
|
||||
Stop()
|
||||
|
|
@ -49,6 +49,24 @@ func NewForwarders() Forwarders {
|
|||
return make(map[string]Forwarder)
|
||||
}
|
||||
|
||||
// IsForwarded checks if pod has a forward
|
||||
func (ff Forwarders) IsPodForwarded(path string) bool {
|
||||
for k := range ff {
|
||||
fqn := strings.Split(k, ":")
|
||||
if fqn[0] == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsForwarded checks if pod has a forward
|
||||
func (ff Forwarders) IsContainerForwarded(path, co string) bool {
|
||||
_, ok := ff[path+":"+co]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// DeleteAll stops and delete all port-forwards.
|
||||
func (ff Forwarders) DeleteAll() {
|
||||
for k, f := range ff {
|
||||
|
|
|
|||
Loading…
Reference in New Issue