parent
7df87a80ab
commit
f7badc4a2a
|
|
@ -0,0 +1,29 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.13.8
|
||||
|
||||
## 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, please 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)
|
||||
|
||||
---
|
||||
|
||||
### GH Sponsorships
|
||||
|
||||
WOOT!! Big Thank you to [Mark Baumann](https://github.com/mtreeman) for your contributions and support for K9s!
|
||||
|
||||
---
|
||||
|
||||
## Resolved Bugs/Features/PRs
|
||||
|
||||
* [Issue #523](https://github.com/derailed/k9s/issues/523)
|
||||
* [Issue #522](https://github.com/derailed/k9s/issues/522)
|
||||
* [Issue #521](https://github.com/derailed/k9s/issues/521)
|
||||
|
||||
---
|
||||
|
||||
<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)
|
||||
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
|
@ -20,16 +21,17 @@ type ShortNames map[string][]string
|
|||
// Aliases represents a collection of aliases.
|
||||
type Aliases struct {
|
||||
Alias Alias `yaml:"alias"`
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
// NewAliases return a new alias.
|
||||
func NewAliases() Aliases {
|
||||
return Aliases{
|
||||
func NewAliases() *Aliases {
|
||||
return &Aliases{
|
||||
Alias: make(Alias, 50),
|
||||
}
|
||||
}
|
||||
|
||||
func (a Aliases) loadDefaults() {
|
||||
func (a *Aliases) loadDefaults() {
|
||||
const (
|
||||
contexts = "contexts"
|
||||
portFwds = "portforwards"
|
||||
|
|
@ -39,6 +41,9 @@ func (a Aliases) loadDefaults() {
|
|||
users = "users"
|
||||
)
|
||||
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
a.Alias["dp"] = "apps/v1/deployments"
|
||||
a.Alias["sec"] = "v1/secrets"
|
||||
a.Alias["jo"] = "batch/v1/jobs"
|
||||
|
|
@ -80,19 +85,52 @@ func (a Aliases) loadDefaults() {
|
|||
}
|
||||
|
||||
// Load K9s aliases.
|
||||
func (a Aliases) Load() error {
|
||||
func (a *Aliases) Load() error {
|
||||
a.loadDefaults()
|
||||
return a.LoadAliases(K9sAlias)
|
||||
}
|
||||
|
||||
// ShortNames return all shortnames.
|
||||
func (a *Aliases) ShortNames() ShortNames {
|
||||
a.mx.RLock()
|
||||
defer a.mx.RUnlock()
|
||||
|
||||
m := make(ShortNames, len(a.Alias))
|
||||
for alias, gvr := range a.Alias {
|
||||
if _, ok := m[gvr]; ok {
|
||||
m[gvr] = append(m[gvr], alias)
|
||||
} else {
|
||||
m[gvr] = []string{alias}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Clear remove all aliases.
|
||||
func (a *Aliases) Clear() {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
for k := range a.Alias {
|
||||
delete(a.Alias, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves an alias.
|
||||
func (a Aliases) Get(k string) (string, bool) {
|
||||
func (a *Aliases) Get(k string) (string, bool) {
|
||||
a.mx.RLock()
|
||||
defer a.mx.RUnlock()
|
||||
|
||||
v, ok := a.Alias[k]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Define declares a new alias.
|
||||
func (a Aliases) Define(gvr string, aliases ...string) {
|
||||
func (a *Aliases) Define(gvr string, aliases ...string) {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
for _, alias := range aliases {
|
||||
if _, ok := a.Alias[alias]; ok {
|
||||
continue
|
||||
|
|
@ -102,17 +140,20 @@ func (a Aliases) Define(gvr string, aliases ...string) {
|
|||
}
|
||||
|
||||
// LoadAliases loads alias from a given file.
|
||||
func (a Aliases) LoadAliases(path string) error {
|
||||
func (a *Aliases) LoadAliases(path string) error {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No custom aliases found")
|
||||
return nil
|
||||
}
|
||||
|
||||
var aa Aliases
|
||||
var aa *Aliases
|
||||
if err := yaml.Unmarshal(f, &aa); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
for k, v := range aa.Alias {
|
||||
a.Alias[k] = v
|
||||
}
|
||||
|
|
@ -121,13 +162,13 @@ func (a Aliases) LoadAliases(path string) error {
|
|||
}
|
||||
|
||||
// Save alias to disk.
|
||||
func (a Aliases) Save() error {
|
||||
func (a *Aliases) Save() error {
|
||||
log.Debug().Msg("[Config] Saving Aliases...")
|
||||
return a.SaveAliases(K9sAlias)
|
||||
}
|
||||
|
||||
// SaveAliases saves aliases to a given file.
|
||||
func (a Aliases) SaveAliases(path string) error {
|
||||
func (a *Aliases) SaveAliases(path string) error {
|
||||
EnsurePath(path, DefaultDirMod)
|
||||
cfg, err := yaml.Marshal(a)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package dao
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ var _ Accessor = (*Alias)(nil)
|
|||
// Alias tracks standard and custom command aliases.
|
||||
type Alias struct {
|
||||
NonResource
|
||||
config.Aliases
|
||||
*config.Aliases
|
||||
}
|
||||
|
||||
// NewAlias returns a new set of aliases.
|
||||
|
|
@ -29,13 +30,6 @@ func NewAlias(f Factory) *Alias {
|
|||
return &a
|
||||
}
|
||||
|
||||
// Clear remove all aliases.
|
||||
func (a *Alias) Clear() {
|
||||
for k := range a.Alias {
|
||||
delete(a.Alias, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Check verifies an alias is defined for this command.
|
||||
func (a *Alias) Check(cmd string) bool {
|
||||
_, ok := a.Aliases.Get(cmd)
|
||||
|
|
@ -43,22 +37,12 @@ func (a *Alias) Check(cmd string) bool {
|
|||
}
|
||||
|
||||
// List returns a collection of aliases.
|
||||
// BOZO!! Already have aliases here. Refact!!
|
||||
func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||
a, ok := ctx.Value(internal.KeyAliases).(*Alias)
|
||||
aa, ok := ctx.Value(internal.KeyAliases).(*Alias)
|
||||
if !ok {
|
||||
return nil, errors.New("no aliases found in context")
|
||||
return nil, fmt.Errorf("expecting *Alias but got %T", ctx.Value(internal.KeyAliases))
|
||||
}
|
||||
|
||||
m := make(config.ShortNames, len(a.Alias))
|
||||
for alias, gvr := range a.Alias {
|
||||
if _, ok := m[gvr]; ok {
|
||||
m[gvr] = append(m[gvr], alias)
|
||||
} else {
|
||||
m[gvr] = []string{alias}
|
||||
}
|
||||
}
|
||||
|
||||
m := aa.ShortNames()
|
||||
oo := make([]runtime.Object, 0, len(m))
|
||||
for gvr, aliases := range m {
|
||||
sort.StringSlice(aliases).Sort()
|
||||
|
|
@ -84,7 +68,7 @@ func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) {
|
|||
|
||||
// Ensure makes sure alias are loaded.
|
||||
func (a *Alias) Ensure() (config.Alias, error) {
|
||||
if err := LoadResources(a.Factory); err != nil {
|
||||
if err := MetaAccess.LoadResources(a.Factory); err != nil {
|
||||
return config.Alias{}, err
|
||||
}
|
||||
return a.Alias, a.load()
|
||||
|
|
@ -95,8 +79,8 @@ func (a *Alias) load() error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, gvr := range AllGVRs() {
|
||||
meta, err := MetaFor(gvr)
|
||||
for _, gvr := range MetaAccess.AllGVRs() {
|
||||
meta, err := MetaAccess.MetaFor(gvr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestAliasList(t *testing.T) {
|
|||
|
||||
func makeAliases() *dao.Alias {
|
||||
return &dao.Alias{
|
||||
Aliases: config.Aliases{
|
||||
Aliases: &config.Aliases{
|
||||
Alias: config.Alias{
|
||||
"fred": "v1/fred",
|
||||
"f": "v1/fred",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -13,7 +14,19 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
var resMetas = ResourceMetas{}
|
||||
// MetaAccess tracks resources metadata.
|
||||
var MetaAccess = NewMeta()
|
||||
|
||||
// Meta represents available resource metas.
|
||||
type Meta struct {
|
||||
resMetas ResourceMetas
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMeta returns a resource meta.
|
||||
func NewMeta() *Meta {
|
||||
return &Meta{resMetas: make(ResourceMetas)}
|
||||
}
|
||||
|
||||
// AccessorFor returns a client accessor for a resource if registered.
|
||||
// Otherwise it returns a generic accessor.
|
||||
|
|
@ -47,14 +60,20 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
|||
}
|
||||
|
||||
// RegisterMeta registers a new resource meta object.
|
||||
func RegisterMeta(gvr string, res metav1.APIResource) {
|
||||
resMetas[client.NewGVR(gvr)] = res
|
||||
func (m *Meta) RegisterMeta(gvr string, res metav1.APIResource) {
|
||||
m.mx.Lock()
|
||||
defer m.mx.Unlock()
|
||||
|
||||
m.resMetas[client.NewGVR(gvr)] = res
|
||||
}
|
||||
|
||||
// AllGVRs returns all cluster resources.
|
||||
func AllGVRs() client.GVRs {
|
||||
kk := make(client.GVRs, 0, len(resMetas))
|
||||
for k := range resMetas {
|
||||
func (m *Meta) AllGVRs() client.GVRs {
|
||||
m.mx.RLock()
|
||||
defer m.mx.RUnlock()
|
||||
|
||||
kk := make(client.GVRs, 0, len(m.resMetas))
|
||||
for k := range m.resMetas {
|
||||
kk = append(kk, k)
|
||||
}
|
||||
sort.Sort(kk)
|
||||
|
|
@ -63,12 +82,15 @@ func AllGVRs() client.GVRs {
|
|||
}
|
||||
|
||||
// MetaFor returns a resource metadata for a given gvr.
|
||||
func MetaFor(gvr client.GVR) (metav1.APIResource, error) {
|
||||
m, ok := resMetas[gvr]
|
||||
func (m *Meta) MetaFor(gvr client.GVR) (metav1.APIResource, error) {
|
||||
m.mx.RLock()
|
||||
defer m.mx.RUnlock()
|
||||
|
||||
meta, ok := m.resMetas[gvr]
|
||||
if !ok {
|
||||
return metav1.APIResource{}, fmt.Errorf("no resource meta defined for %q", gvr)
|
||||
}
|
||||
return m, nil
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// IsK8sMeta checks for non resource meta.
|
||||
|
|
@ -94,13 +116,16 @@ func IsK9sMeta(m metav1.APIResource) bool {
|
|||
}
|
||||
|
||||
// LoadResources hydrates server preferred+CRDs resource metadata.
|
||||
func LoadResources(f Factory) error {
|
||||
resMetas = make(ResourceMetas, 100)
|
||||
if err := loadPreferred(f, resMetas); err != nil {
|
||||
func (m *Meta) LoadResources(f Factory) error {
|
||||
m.mx.Lock()
|
||||
defer m.mx.Unlock()
|
||||
|
||||
m.resMetas = make(ResourceMetas, 100)
|
||||
if err := loadPreferred(f, m.resMetas); err != nil {
|
||||
return err
|
||||
}
|
||||
loadNonResource(resMetas)
|
||||
loadCRDs(f, resMetas)
|
||||
loadNonResource(m.resMetas)
|
||||
loadCRDs(f, m.resMetas)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import (
|
|||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
const logMaxBufferSize = 50
|
||||
|
||||
// LogsListener represents a log model listener.
|
||||
type LogsListener interface {
|
||||
// LogChanged notifies the model changed.
|
||||
|
|
@ -65,8 +67,10 @@ func (l *Log) Init(f dao.Factory) {
|
|||
// Clear the logs.
|
||||
func (l *Log) Clear() {
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
l.lines, l.lastSent = []string{}, 0
|
||||
{
|
||||
l.lines, l.lastSent = []string{}, 0
|
||||
}
|
||||
l.mx.Unlock()
|
||||
l.fireLogCleared()
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +78,7 @@ func (l *Log) Clear() {
|
|||
func (l *Log) Start() {
|
||||
if err := l.load(); err != nil {
|
||||
log.Error().Err(err).Msgf("Tail logs failed!")
|
||||
l.fireLogError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,6 +96,7 @@ func (l *Log) Stop() {
|
|||
func (l *Log) Set(lines []string) {
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
|
||||
l.lines = lines
|
||||
l.fireLogChanged(lines)
|
||||
}
|
||||
|
|
@ -99,6 +105,7 @@ func (l *Log) Set(lines []string) {
|
|||
func (l *Log) ClearFilter() {
|
||||
l.mx.RLock()
|
||||
defer l.mx.RUnlock()
|
||||
|
||||
l.filter = ""
|
||||
l.fireLogChanged(l.lines)
|
||||
}
|
||||
|
|
@ -133,7 +140,7 @@ func (l *Log) load() error {
|
|||
}
|
||||
logger, ok := accessor.(dao.Loggable)
|
||||
if !ok {
|
||||
return fmt.Errorf("Resource %s is not tailable", l.gvr)
|
||||
return fmt.Errorf("Resource %s is not Loggable", l.gvr)
|
||||
}
|
||||
|
||||
if err := logger.TailLogs(ctx, c, l.logOptions); err != nil {
|
||||
|
|
@ -152,6 +159,7 @@ func (l *Log) Append(line string) {
|
|||
if line == "" {
|
||||
return
|
||||
}
|
||||
|
||||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
|
||||
|
|
@ -196,6 +204,15 @@ func (l *Log) updateLogs(ctx context.Context, c <-chan string) {
|
|||
return
|
||||
}
|
||||
l.Append(line)
|
||||
var overflow bool
|
||||
l.mx.RLock()
|
||||
{
|
||||
overflow = len(l.lines)-l.lastSent > logMaxBufferSize
|
||||
}
|
||||
l.mx.RUnlock()
|
||||
if overflow {
|
||||
l.Notify(true)
|
||||
}
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
l.Notify(true)
|
||||
case <-ctx.Done():
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ func TestLogStartStop(t *testing.T) {
|
|||
|
||||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, 1, v.errCalled)
|
||||
assert.Equal(t, 2, len(v.data))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
|
|
@ -40,6 +42,7 @@ type StackListener interface {
|
|||
type Stack struct {
|
||||
components []Component
|
||||
listeners []StackListener
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStack returns a new initialized stack.
|
||||
|
|
@ -49,6 +52,9 @@ func NewStack() *Stack {
|
|||
|
||||
// Flatten returns a string representation of the component stack.
|
||||
func (s *Stack) Flatten() []string {
|
||||
s.mx.RLock()
|
||||
defer s.mx.RUnlock()
|
||||
|
||||
ss := make([]string, len(s.components))
|
||||
for i, c := range s.components {
|
||||
ss[i] = c.Name()
|
||||
|
|
@ -84,7 +90,12 @@ func (s *Stack) Push(c Component) {
|
|||
if top := s.Top(); top != nil {
|
||||
top.Stop()
|
||||
}
|
||||
s.components = append(s.components, c)
|
||||
|
||||
s.mx.Lock()
|
||||
{
|
||||
s.components = append(s.components, c)
|
||||
}
|
||||
s.mx.Unlock()
|
||||
s.notify(StackPush, c)
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +105,13 @@ func (s *Stack) Pop() (Component, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
c := s.components[s.size()]
|
||||
s.components = s.components[:s.size()]
|
||||
var c Component
|
||||
s.mx.Lock()
|
||||
{
|
||||
c = s.components[s.size()]
|
||||
s.components = s.components[:s.size()]
|
||||
}
|
||||
s.mx.Unlock()
|
||||
s.notify(StackPop, c)
|
||||
|
||||
return c, true
|
||||
|
|
@ -103,6 +119,9 @@ func (s *Stack) Pop() (Component, bool) {
|
|||
|
||||
// Peek returns stack state.
|
||||
func (s *Stack) Peek() []Component {
|
||||
s.mx.RLock()
|
||||
defer s.mx.RUnlock()
|
||||
|
||||
return s.components
|
||||
}
|
||||
|
||||
|
|
@ -115,6 +134,9 @@ func (s *Stack) Clear() {
|
|||
|
||||
// Empty returns true if the stack is empty.
|
||||
func (s *Stack) Empty() bool {
|
||||
s.mx.RLock()
|
||||
defer s.mx.RUnlock()
|
||||
|
||||
return len(s.components) == 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package model
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ type Table struct {
|
|||
inUpdate int32
|
||||
refreshRate time.Duration
|
||||
instance string
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
// NewTable returns a new table model.
|
||||
|
|
@ -170,7 +172,10 @@ func (t *Table) Empty() bool {
|
|||
|
||||
// Peek returns model data.
|
||||
func (t *Table) Peek() render.TableData {
|
||||
return *t.data
|
||||
t.mx.RLock()
|
||||
defer t.mx.RUnlock()
|
||||
|
||||
return t.data.Clone()
|
||||
}
|
||||
|
||||
func (t *Table) updater(ctx context.Context) {
|
||||
|
|
@ -200,7 +205,7 @@ func (t *Table) refresh(ctx context.Context) {
|
|||
t.fireTableLoadFailed(err)
|
||||
return
|
||||
}
|
||||
t.fireTableChanged(*t.data)
|
||||
t.fireTableChanged()
|
||||
}
|
||||
|
||||
func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, error) {
|
||||
|
|
@ -252,15 +257,16 @@ func (t *Table) reconcile(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
t.data.Mutex.Lock()
|
||||
defer t.data.Mutex.Unlock()
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
||||
// if labelSelector in place might as well clear the model data.
|
||||
sel, ok := ctx.Value(internal.KeyLabels).(string)
|
||||
if ok && sel != "" {
|
||||
t.data.Clear()
|
||||
}
|
||||
t.data.Update(rows)
|
||||
t.data.Namespace, t.data.Header = t.namespace, meta.Renderer.Header(t.namespace)
|
||||
t.data.SetHeader(t.namespace, meta.Renderer.Header(t.namespace))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -292,7 +298,8 @@ func (t *Table) resourceMeta() ResourceMeta {
|
|||
return meta
|
||||
}
|
||||
|
||||
func (t *Table) fireTableChanged(data render.TableData) {
|
||||
func (t *Table) fireTableChanged() {
|
||||
data := t.Peek()
|
||||
for _, l := range t.listeners {
|
||||
l.TableDataChanged(data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TableData tracks a K8s resource for tabular display.
|
||||
type TableData struct {
|
||||
Header HeaderRow
|
||||
RowEvents RowEvents
|
||||
Namespace string
|
||||
Mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewTableData returns a new table.
|
||||
func NewTableData() *TableData {
|
||||
return &TableData{Mutex: &sync.RWMutex{}}
|
||||
return &TableData{}
|
||||
}
|
||||
|
||||
// Clear clears out the entire table.
|
||||
|
|
@ -31,13 +26,18 @@ func cloneTable(t TableData) TableData {
|
|||
return t
|
||||
}
|
||||
|
||||
// SetHeader sets table header.
|
||||
func (t *TableData) SetHeader(ns string, h HeaderRow) {
|
||||
t.Namespace, t.Header = ns, h
|
||||
}
|
||||
|
||||
// Update computes row deltas and update the table data.
|
||||
func (t *TableData) Update(rows Rows) {
|
||||
empty := len(t.RowEvents) == 0
|
||||
kk := make([]string, 0, len(rows))
|
||||
kk := make(map[string]struct{}, len(rows))
|
||||
var blankDelta DeltaRow
|
||||
for _, row := range rows {
|
||||
kk = append(kk, row.ID)
|
||||
kk[row.ID] = struct{}{}
|
||||
if empty {
|
||||
t.RowEvents = append(t.RowEvents, NewRowEvent(EventAdd, row))
|
||||
continue
|
||||
|
|
@ -61,19 +61,11 @@ func (t *TableData) Update(rows Rows) {
|
|||
}
|
||||
}
|
||||
|
||||
// Delete delete items in cache that are no longer valid.
|
||||
func (t *TableData) Delete(newKeys []string) {
|
||||
// Delete removes items in cache that are no longer valid.
|
||||
func (t *TableData) Delete(newKeys map[string]struct{}) {
|
||||
var victims []string
|
||||
for _, re := range t.RowEvents {
|
||||
var found bool
|
||||
for i, key := range newKeys {
|
||||
if key == re.Row.ID {
|
||||
found = true
|
||||
newKeys = append(newKeys[:i], newKeys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if _, ok := newKeys[re.Row.ID]; !ok {
|
||||
victims = append(victims, re.Row.ID)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,12 +80,10 @@ func (t *TableData) Diff(table TableData) bool {
|
|||
if t.Namespace != table.Namespace {
|
||||
return true
|
||||
}
|
||||
|
||||
if t.Header.Diff(table.Header) {
|
||||
return true
|
||||
}
|
||||
if t.RowEvents.Diff(table.RowEvents) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return t.RowEvents.Diff(table.RowEvents)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
func TestTableDataDelete(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
re render.RowEvents
|
||||
kk []string
|
||||
kk map[string]struct{}
|
||||
e render.RowEvents
|
||||
}{
|
||||
"ordered": {
|
||||
|
|
@ -19,7 +19,7 @@ func TestTableDataDelete(t *testing.T) {
|
|||
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||
},
|
||||
kk: []string{"A", "C"},
|
||||
kk: map[string]struct{}{"A": struct{}{}, "C": struct{}{}},
|
||||
e: render.RowEvents{
|
||||
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||
|
|
@ -32,7 +32,7 @@ func TestTableDataDelete(t *testing.T) {
|
|||
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||
{Row: render.Row{ID: "D", Fields: render.Fields{"10", "2", "3"}}},
|
||||
},
|
||||
kk: []string{"C", "A"},
|
||||
kk: map[string]struct{}{"C": struct{}{}, "A": struct{}{}},
|
||||
e: render.RowEvents{
|
||||
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||
|
|
|
|||
|
|
@ -163,9 +163,6 @@ func (t *Table) SetSortCol(index, count int, asc bool) {
|
|||
|
||||
// Update table content.
|
||||
func (t *Table) Update(data render.TableData) {
|
||||
data.Mutex.RLock()
|
||||
defer data.Mutex.RUnlock()
|
||||
|
||||
if t.decorateFn != nil {
|
||||
data = t.decorateFn(data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
|
|
@ -40,6 +41,7 @@ type App struct {
|
|||
cancelFn context.CancelFunc
|
||||
conRetry int
|
||||
clusterModel *model.ClusterInfo
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
// NewApp returns a K9s app instance.
|
||||
|
|
@ -59,6 +61,8 @@ func NewApp(cfg *config.Config) *App {
|
|||
|
||||
// ConOK checks the connection is cool, returns false otherwise.
|
||||
func (a *App) ConOK() bool {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
return a.conRetry == 0
|
||||
}
|
||||
|
||||
|
|
@ -194,6 +198,9 @@ func (a *App) clusterUpdater(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (a *App) refreshCluster() {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
c := a.Content.Top()
|
||||
if ok := a.Conn().CheckConnectivity(); ok {
|
||||
if a.conRetry > 0 {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func NewBrowser(gvr client.GVR) ResourceViewer {
|
|||
// Init watches all running pods in given namespace
|
||||
func (b *Browser) Init(ctx context.Context) error {
|
||||
var err error
|
||||
b.meta, err = dao.MetaFor(b.gvr)
|
||||
b.meta, err = dao.MetaAccess.MetaFor(b.gvr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ func (b *Browser) Init(ctx context.Context) error {
|
|||
return e
|
||||
}
|
||||
}
|
||||
b.app.CmdBuff().Reset()
|
||||
|
||||
b.bindKeys()
|
||||
if b.bindKeysFn != nil {
|
||||
|
|
|
|||
|
|
@ -69,11 +69,6 @@ func (c *Container) selectedContainer() string {
|
|||
}
|
||||
|
||||
func (c *Container) viewLogs(app *App, model ui.Tabular, gvr, path string) {
|
||||
status := c.GetTable().GetSelectedCell(3)
|
||||
if status != "Running" && status != "Completed" {
|
||||
app.Flash().Err(errors.New("No logs available"))
|
||||
return
|
||||
}
|
||||
c.ResourceViewer.(*LogsExtender).showLogs(c.GetTable().Path, false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ func (l *Log) LogCleared() {
|
|||
|
||||
// LogFailed notifies an error occurred.
|
||||
func (l *Log) LogFailed(err error) {
|
||||
l.app.Flash().Err(err)
|
||||
l.app.QueueUpdateDraw(func() {
|
||||
l.app.Flash().Err(err)
|
||||
})
|
||||
}
|
||||
|
||||
// LogChanged updates the logs.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
dao.RegisterMeta("v1/pods", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("v1/pods", metav1.APIResource{
|
||||
Name: "pods",
|
||||
SingularName: "pod",
|
||||
Namespaced: true,
|
||||
|
|
@ -19,7 +19,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("v1/namespaces", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("v1/namespaces", metav1.APIResource{
|
||||
Name: "namespaces",
|
||||
SingularName: "namespace",
|
||||
Namespaced: true,
|
||||
|
|
@ -27,7 +27,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("v1/services", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("v1/services", metav1.APIResource{
|
||||
Name: "services",
|
||||
SingularName: "service",
|
||||
Namespaced: true,
|
||||
|
|
@ -35,7 +35,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("v1/secrets", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("v1/secrets", metav1.APIResource{
|
||||
Name: "secrets",
|
||||
SingularName: "secret",
|
||||
Namespaced: true,
|
||||
|
|
@ -44,7 +44,7 @@ func init() {
|
|||
Categories: []string{"k9s"},
|
||||
})
|
||||
|
||||
dao.RegisterMeta("aliases", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("aliases", metav1.APIResource{
|
||||
Name: "aliases",
|
||||
SingularName: "alias",
|
||||
Namespaced: true,
|
||||
|
|
@ -52,7 +52,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("containers", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("containers", metav1.APIResource{
|
||||
Name: "containers",
|
||||
SingularName: "container",
|
||||
Namespaced: true,
|
||||
|
|
@ -60,7 +60,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("contexts", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("contexts", metav1.APIResource{
|
||||
Name: "contexts",
|
||||
SingularName: "context",
|
||||
Namespaced: true,
|
||||
|
|
@ -68,7 +68,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("subjects", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("subjects", metav1.APIResource{
|
||||
Name: "subjects",
|
||||
SingularName: "subject",
|
||||
Namespaced: true,
|
||||
|
|
@ -76,7 +76,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("rbac", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("rbac", metav1.APIResource{
|
||||
Name: "rbacs",
|
||||
SingularName: "rbac",
|
||||
Namespaced: true,
|
||||
|
|
@ -84,7 +84,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("portforwards", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("portforwards", metav1.APIResource{
|
||||
Name: "portforwards",
|
||||
SingularName: "portforward",
|
||||
Namespaced: true,
|
||||
|
|
@ -93,7 +93,7 @@ func init() {
|
|||
Categories: []string{"k9s"},
|
||||
})
|
||||
|
||||
dao.RegisterMeta("screendumps", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("screendumps", metav1.APIResource{
|
||||
Name: "screendumps",
|
||||
SingularName: "screendump",
|
||||
Namespaced: true,
|
||||
|
|
@ -101,7 +101,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("apps/v1/statefulsets", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("apps/v1/statefulsets", metav1.APIResource{
|
||||
Name: "statefulsets",
|
||||
SingularName: "statefulset",
|
||||
Namespaced: true,
|
||||
|
|
@ -109,7 +109,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("apps/v1/daemonsets", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("apps/v1/daemonsets", metav1.APIResource{
|
||||
Name: "daemonsets",
|
||||
SingularName: "daemonset",
|
||||
Namespaced: true,
|
||||
|
|
@ -117,7 +117,7 @@ func init() {
|
|||
Verbs: []string{"get", "list", "watch", "delete"},
|
||||
Categories: []string{"k9s"},
|
||||
})
|
||||
dao.RegisterMeta("apps/v1/deployments", metav1.APIResource{
|
||||
dao.MetaAccess.RegisterMeta("apps/v1/deployments", metav1.APIResource{
|
||||
Name: "deployments",
|
||||
SingularName: "deployment",
|
||||
Namespaced: true,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func (x *Xray) Init(ctx context.Context) error {
|
|||
x.SetKeyListenerFn(x.keyEntered)
|
||||
|
||||
var err error
|
||||
x.meta, err = dao.MetaFor(x.gvr)
|
||||
x.meta, err = dao.MetaAccess.MetaFor(x.gvr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ func (x *Xray) refreshActions() {
|
|||
}
|
||||
|
||||
var err error
|
||||
x.meta, err = dao.MetaFor(client.NewGVR(ref.GVR))
|
||||
x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(ref.GVR))
|
||||
if err != nil {
|
||||
log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err)
|
||||
return
|
||||
|
|
@ -288,7 +288,7 @@ func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
defer x.Start()
|
||||
{
|
||||
gvr := client.NewGVR(ref.GVR)
|
||||
meta, err := dao.MetaFor(gvr)
|
||||
meta, err := dao.MetaAccess.MetaFor(gvr)
|
||||
if err != nil {
|
||||
log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ func (f *Factory) SetActiveNS(ns string) {
|
|||
}
|
||||
|
||||
func (f *Factory) isClusterWide() bool {
|
||||
f.mx.RLock()
|
||||
defer f.mx.RUnlock()
|
||||
|
||||
_, ok := f.factories[client.AllNamespaces]
|
||||
return ok
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ func dumpStdOut(n *TreeNode, level int) {
|
|||
}
|
||||
|
||||
func category(gvr string) string {
|
||||
meta, err := dao.MetaFor(client.NewGVR(gvr))
|
||||
meta, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue