refact and rename

mine
derailed 2019-04-11 23:03:26 -06:00
parent a89a594893
commit bb42c39645
69 changed files with 1241 additions and 439 deletions

View File

@ -0,0 +1,39 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.4.9
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Popeye
Turns out [Popeye](https://github.com/derailed/popeye) is in too much flux at present, thus I've decided to remove it from K9s for the time being.
### ContainerView
Added a container view to list all the containers available on a given pod. On a selected pod, you can now press `<enter>` to view all of it's associated containers. Once in container view pressing `<enter>` on a selected container, will show the container logs.
---
## Resolved Bugs
+ [Issue #163](https://github.com/derailed/k9s/issues/163)
+ [Issue #162](https://github.com/derailed/k9s/issues/162)
+ [Issue #39](https://github.com/derailed/k9s/issues/39)
+ [Issue #27](https://github.com/derailed/k9s/issues/27)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

1
go.mod
View File

@ -23,4 +23,5 @@ require (
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f
sigs.k8s.io/structured-merge-diff v0.0.0-20190404181321-646549c5a231 // indirect
sigs.k8s.io/yaml v1.1.0
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787
)

2
go.sum
View File

@ -373,3 +373,5 @@ sigs.k8s.io/structured-merge-diff v0.0.0-20190404181321-646549c5a231 h1:jn9MygT5
sigs.k8s.io/structured-merge-diff v0.0.0-20190404181321-646549c5a231/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ=
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=

View File

@ -38,6 +38,8 @@ type (
Get(ns string, name string) (interface{}, error)
List(ns string) (Collection, error)
Delete(ns string, name string) error
SetFieldSelector(string)
SetLabelSelector(string)
}
// Connection represents a Kubenetes apiserver connection.
@ -78,8 +80,7 @@ func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient {
return &conn
}
// ValidNamespaces returns a collection of valid namespaces.
// Bozo!! filter active?
// ValidNamespaces returns all available namespaces.
func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
nn, err := a.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {

16
internal/k8s/base.go Normal file
View File

@ -0,0 +1,16 @@
package k8s
type base struct {
fieldSelector string
labelSelector string
}
// SetFieldSelector refines query results via selector.
func (b *base) SetFieldSelector(s string) {
b.fieldSelector = s
}
// SetLabelSelector refines query results via labels.
func (b *base) SetLabelSelector(s string) {
b.labelSelector = s
}

View File

@ -6,12 +6,13 @@ import (
// ClusterRole represents a Kubernetes ClusterRole
type ClusterRole struct {
*base
Connection
}
// NewClusterRole returns a new ClusterRole.
func NewClusterRole(c Connection) Cruder {
return &ClusterRole{c}
func NewClusterRole(c Connection) *ClusterRole {
return &ClusterRole{&base{}, c}
}
// Get a cluster role.
@ -21,7 +22,11 @@ func (c *ClusterRole) Get(_, n string) (interface{}, error) {
// List all ClusterRoles on a cluster.
func (c *ClusterRole) List(_ string) (Collection, error) {
rr, err := c.DialOrDie().RbacV1().ClusterRoles().List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: c.labelSelector,
FieldSelector: c.fieldSelector,
}
rr, err := c.DialOrDie().RbacV1().ClusterRoles().List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// ClusterRoleBinding represents a Kubernetes ClusterRoleBinding
type ClusterRoleBinding struct {
*base
Connection
}
// NewClusterRoleBinding returns a new ClusterRoleBinding.
func NewClusterRoleBinding(c Connection) Cruder {
return &ClusterRoleBinding{c}
func NewClusterRoleBinding(c Connection) *ClusterRoleBinding {
return &ClusterRoleBinding{&base{}, c}
}
// Get a service.
@ -21,7 +22,11 @@ func (c *ClusterRoleBinding) Get(_, n string) (interface{}, error) {
// List all ClusterRoleBindings on a cluster.
func (c *ClusterRoleBinding) List(_ string) (Collection, error) {
rr, err := c.DialOrDie().RbacV1().ClusterRoleBindings().List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: c.labelSelector,
FieldSelector: c.fieldSelector,
}
rr, err := c.DialOrDie().RbacV1().ClusterRoleBindings().List(opts)
if err != nil {
return Collection{}, err
}

View File

@ -6,12 +6,13 @@ import (
// ConfigMap represents a Kubernetes ConfigMap
type ConfigMap struct {
*base
Connection
}
// NewConfigMap returns a new ConfigMap.
func NewConfigMap(c Connection) Cruder {
return &ConfigMap{c}
func NewConfigMap(c Connection) *ConfigMap {
return &ConfigMap{&base{}, c}
}
// Get a ConfigMap.
@ -21,7 +22,11 @@ func (c *ConfigMap) Get(ns, n string) (interface{}, error) {
// List all ConfigMaps in a given namespace.
func (c *ConfigMap) List(ns string) (Collection, error) {
rr, err := c.DialOrDie().CoreV1().ConfigMaps(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: c.labelSelector,
FieldSelector: c.fieldSelector,
}
rr, err := c.DialOrDie().CoreV1().ConfigMaps(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -32,12 +32,13 @@ func (c *NamedContext) MustCurrentContextName() string {
// Context represents a Kubernetes Context.
type Context struct {
*base
Connection
}
// NewContext returns a new Context.
func NewContext(c Connection) Cruder {
return &Context{c}
func NewContext(c Connection) *Context {
return &Context{&base{}, c}
}
// Get a Context.

View File

@ -4,24 +4,29 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// CRD represents a Kubernetes CRD
type CRD struct {
// CustomResourceDefinition represents a Kubernetes CustomResourceDefinition
type CustomResourceDefinition struct {
*base
Connection
}
// NewCRD returns a new CRD.
func NewCRD(c Connection) Cruder {
return &CRD{c}
// NewCustomResourceDefinition returns a new CustomResourceDefinition.
func NewCustomResourceDefinition(c Connection) *CustomResourceDefinition {
return &CustomResourceDefinition{&base{}, c}
}
// Get a CRD.
func (c *CRD) Get(_, n string) (interface{}, error) {
// Get a CustomResourceDefinition.
func (c *CustomResourceDefinition) Get(_, n string) (interface{}, error) {
return c.NSDialOrDie().Get(n, metav1.GetOptions{})
}
// List all CRDs in a given namespace.
func (c *CRD) List(string) (Collection, error) {
rr, err := c.NSDialOrDie().List(metav1.ListOptions{})
// List all CustomResourceDefinitions in a given namespace.
func (c *CustomResourceDefinition) List(string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: c.labelSelector,
FieldSelector: c.fieldSelector,
}
rr, err := c.NSDialOrDie().List(opts)
if err != nil {
return nil, err
}
@ -33,7 +38,7 @@ func (c *CRD) List(string) (Collection, error) {
return cc, nil
}
// Delete a CRD.
func (c *CRD) Delete(_, n string) error {
// Delete a CustomResourceDefinition.
func (c *CustomResourceDefinition) Delete(_, n string) error {
return c.NSDialOrDie().Delete(n, nil)
}

View File

@ -11,12 +11,13 @@ const maxJobNameSize = 42
// CronJob represents a Kubernetes CronJob.
type CronJob struct {
*base
Connection
}
// NewCronJob returns a new CronJob.
func NewCronJob(c Connection) Cruder {
return &CronJob{c}
func NewCronJob(c Connection) *CronJob {
return &CronJob{&base{}, c}
}
// Get a CronJob.
@ -26,7 +27,11 @@ func (c *CronJob) Get(ns, n string) (interface{}, error) {
// List all CronJobs in a given namespace.
func (c *CronJob) List(ns string) (Collection, error) {
rr, err := c.DialOrDie().BatchV1beta1().CronJobs(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: c.labelSelector,
FieldSelector: c.fieldSelector,
}
rr, err := c.DialOrDie().BatchV1beta1().CronJobs(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// Deployment represents a Kubernetes Deployment.
type Deployment struct {
*base
Connection
}
// NewDeployment returns a new Deployment.
func NewDeployment(c Connection) Cruder {
return &Deployment{c}
func NewDeployment(c Connection) *Deployment {
return &Deployment{&base{}, c}
}
// Get a deployment.
@ -21,7 +22,11 @@ func (d *Deployment) Get(ns, n string) (interface{}, error) {
// List all Deployments in a given namespace.
func (d *Deployment) List(ns string) (Collection, error) {
rr, err := d.DialOrDie().Apps().Deployments(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: d.labelSelector,
FieldSelector: d.fieldSelector,
}
rr, err := d.DialOrDie().Apps().Deployments(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// DaemonSet represents a Kubernetes DaemonSet
type DaemonSet struct {
*base
Connection
}
// NewDaemonSet returns a new DaemonSet.
func NewDaemonSet(c Connection) Cruder {
return &DaemonSet{c}
func NewDaemonSet(c Connection) *DaemonSet {
return &DaemonSet{&base{}, c}
}
// Get a DaemonSet.
@ -21,7 +22,11 @@ func (d *DaemonSet) Get(ns, n string) (interface{}, error) {
// List all DaemonSets in a given namespace.
func (d *DaemonSet) List(ns string) (Collection, error) {
rr, err := d.DialOrDie().ExtensionsV1beta1().DaemonSets(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: d.labelSelector,
FieldSelector: d.fieldSelector,
}
rr, err := d.DialOrDie().ExtensionsV1beta1().DaemonSets(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// Endpoints represents a Kubernetes Endpoints.
type Endpoints struct {
*base
Connection
}
// NewEndpoints returns a new Endpoints.
func NewEndpoints(c Connection) Cruder {
return &Endpoints{c}
func NewEndpoints(c Connection) *Endpoints {
return &Endpoints{&base{}, c}
}
// Get a Endpoint.
@ -21,7 +22,11 @@ func (e *Endpoints) Get(ns, n string) (interface{}, error) {
// List all Endpoints in a given namespace.
func (e *Endpoints) List(ns string) (Collection, error) {
rr, err := e.DialOrDie().CoreV1().Endpoints(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: e.labelSelector,
FieldSelector: e.fieldSelector,
}
rr, err := e.DialOrDie().CoreV1().Endpoints(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// Event represents a Kubernetes Event.
type Event struct {
*base
Connection
}
// NewEvent returns a new Event.
func NewEvent(c Connection) Cruder {
return &Event{c}
func NewEvent(c Connection) *Event {
return &Event{&base{}, c}
}
// Get a Event.
@ -21,7 +22,11 @@ func (e *Event) Get(ns, n string) (interface{}, error) {
// List all Events in a given namespace.
func (e *Event) List(ns string) (Collection, error) {
rr, err := e.DialOrDie().CoreV1().Events(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: e.labelSelector,
FieldSelector: e.fieldSelector,
}
rr, err := e.DialOrDie().CoreV1().Events(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,7 +6,8 @@ import (
const megaByte = 1024 * 1024
func asMi(v int64) float64 {
// ToMB converts bytes to megabytes.
func ToMB(v int64) float64 {
return float64(v) / megaByte
}

View File

@ -20,7 +20,7 @@ func TestToPerc(t *testing.T) {
}
}
func TestAsMi(t *testing.T) {
func TestToMB(t *testing.T) {
uu := []struct {
v int64
e float64
@ -31,6 +31,6 @@ func TestAsMi(t *testing.T) {
}
for _, u := range uu {
assert.Equal(t, u.e, asMi(u.v))
assert.Equal(t, u.e, ToMB(u.v))
}
}

View File

@ -4,24 +4,29 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// HPAV1 represents am HorizontalPodAutoscaler.
type HPAV1 struct {
// HorizontalPodAutoscalerV1 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV1 struct {
*base
Connection
}
// NewHPAV1 returns a new HPA.
func NewHPAV1(c Connection) Cruder {
return &HPAV1{c}
// NewHorizontalPodAutoscalerV1 returns a new HorizontalPodAutoscaler.
func NewHorizontalPodAutoscalerV1(c Connection) *HorizontalPodAutoscalerV1 {
return &HorizontalPodAutoscalerV1{&base{}, c}
}
// Get a HPA.
func (h *HPAV1) Get(ns, n string) (interface{}, error) {
// Get a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV1) Get(ns, n string) (interface{}, error) {
return h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{})
}
// List all HPAs in a given namespace.
func (h *HPAV1) List(ns string) (Collection, error) {
rr, err := h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).List(metav1.ListOptions{})
// List all HorizontalPodAutoscalers in a given namespace.
func (h *HorizontalPodAutoscalerV1) List(ns string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: h.labelSelector,
FieldSelector: h.fieldSelector,
}
rr, err := h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).List(opts)
if err != nil {
return nil, err
}
@ -32,7 +37,7 @@ func (h *HPAV1) List(ns string) (Collection, error) {
return cc, nil
}
// Delete a HPA.
func (h *HPAV1) Delete(ns, n string) error {
// Delete a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV1) Delete(ns, n string) error {
return h.DialOrDie().AutoscalingV1().HorizontalPodAutoscalers(ns).Delete(n, nil)
}

View File

@ -4,24 +4,29 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// HPAV2Beta1 represents am HorizontalPodAutoscaler.
type HPAV2Beta1 struct {
// HorizontalPodAutoscalerV2Beta1 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV2Beta1 struct {
*base
Connection
}
// NewHPAV2Beta1 returns a new HPA.
func NewHPAV2Beta1(c Connection) Cruder {
return &HPAV2Beta1{c}
// NewHorizontalPodAutoscalerV2Beta1 returns a new HorizontalPodAutoscaler.
func NewHorizontalPodAutoscalerV2Beta1(c Connection) *HorizontalPodAutoscalerV2Beta1 {
return &HorizontalPodAutoscalerV2Beta1{&base{}, c}
}
// Get a HPA.
func (h *HPAV2Beta1) Get(ns, n string) (interface{}, error) {
// Get a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV2Beta1) Get(ns, n string) (interface{}, error) {
return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{})
}
// List all HPAs in a given namespace.
func (h *HPAV2Beta1) List(ns string) (Collection, error) {
rr, err := h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(metav1.ListOptions{})
// List all HorizontalPodAutoscalers in a given namespace.
func (h *HorizontalPodAutoscalerV2Beta1) List(ns string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: h.labelSelector,
FieldSelector: h.fieldSelector,
}
rr, err := h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(opts)
if err != nil {
return nil, err
}
@ -32,7 +37,7 @@ func (h *HPAV2Beta1) List(ns string) (Collection, error) {
return cc, nil
}
// Delete a HPA.
func (h *HPAV2Beta1) Delete(ns, n string) error {
// Delete a HorizontalPodAutoscaler.
func (h *HorizontalPodAutoscalerV2Beta1) Delete(ns, n string) error {
return h.DialOrDie().AutoscalingV2beta1().HorizontalPodAutoscalers(ns).Delete(n, nil)
}

View File

@ -6,24 +6,29 @@ import (
var supportedAutoScalingAPIVersions = []string{"v2beta2", "v2beta1", "v1"}
// HPAV2Beta2 represents am HorizontalPodAutoscaler.
type HPAV2Beta2 struct {
// HorizontalPodAutoscalerV2Beta2 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV2Beta2 struct {
*base
Connection
}
// NewHPAV2Beta2 returns a new HPAV2Beta2.
func NewHPAV2Beta2(c Connection) Cruder {
return &HPAV2Beta2{c}
// NewHorizontalPodAutoscalerV2Beta2 returns a new HorizontalPodAutoscalerV2Beta2.
func NewHorizontalPodAutoscalerV2Beta2(c Connection) *HorizontalPodAutoscalerV2Beta2 {
return &HorizontalPodAutoscalerV2Beta2{&base{}, c}
}
// Get a HPAV2Beta2.
func (h *HPAV2Beta2) Get(ns, n string) (interface{}, error) {
// Get a HorizontalPodAutoscalerV2Beta2.
func (h *HorizontalPodAutoscalerV2Beta2) Get(ns, n string) (interface{}, error) {
return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{})
}
// List all HPAV2Beta2s in a given namespace.
func (h *HPAV2Beta2) List(ns string) (Collection, error) {
rr, err := h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(metav1.ListOptions{})
// List all HorizontalPodAutoscalerV2Beta2s in a given namespace.
func (h *HorizontalPodAutoscalerV2Beta2) List(ns string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: h.labelSelector,
FieldSelector: h.fieldSelector,
}
rr, err := h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(opts)
if err != nil {
return nil, err
}
@ -34,7 +39,7 @@ func (h *HPAV2Beta2) List(ns string) (Collection, error) {
return cc, nil
}
// Delete a HPAV2Beta2.
func (h *HPAV2Beta2) Delete(ns, n string) error {
// Delete a HorizontalPodAutoscalerV2Beta2.
func (h *HorizontalPodAutoscalerV2Beta2) Delete(ns, n string) error {
return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Delete(n, nil)
}

View File

@ -6,12 +6,13 @@ import (
// Ingress represents a Kubernetes Ingress.
type Ingress struct {
*base
Connection
}
// NewIngress returns a new Ingress.
func NewIngress(c Connection) Cruder {
return &Ingress{c}
func NewIngress(c Connection) *Ingress {
return &Ingress{&base{}, c}
}
// Get a Ingress.
@ -21,7 +22,11 @@ func (i *Ingress) Get(ns, n string) (interface{}, error) {
// List all Ingresss in a given namespace.
func (i *Ingress) List(ns string) (Collection, error) {
rr, err := i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: i.labelSelector,
FieldSelector: i.fieldSelector,
}
rr, err := i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -12,6 +12,7 @@ import (
type (
// Job represents a Kubernetes Job.
Job struct {
*base
Connection
}
@ -23,8 +24,8 @@ type (
)
// NewJob returns a new Job.
func NewJob(c Connection) Cruder {
return &Job{c}
func NewJob(c Connection) *Job {
return &Job{&base{}, c}
}
// Get a Job.
@ -34,7 +35,11 @@ func (j *Job) Get(ns, n string) (interface{}, error) {
// List all Jobs in a given namespace.
func (j *Job) List(ns string) (Collection, error) {
rr, err := j.DialOrDie().BatchV1().Jobs(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: j.labelSelector,
FieldSelector: j.fieldSelector,
}
rr, err := j.DialOrDie().BatchV1().Jobs(ns).List(opts)
if err != nil {
return nil, err
}
@ -57,7 +62,7 @@ func (j *Job) Containers(ns, n string, includeInit bool) ([]string, error) {
if err != nil {
return nil, err
}
return NewPod(j).(Loggable).Containers(ns, pod, includeInit)
return NewPod(j).Containers(ns, pod, includeInit)
}
// Logs fetch container logs for a given job and container.
@ -66,7 +71,7 @@ func (j *Job) Logs(ns, n, co string, lines int64, prev bool) *restclient.Request
if err != nil {
return nil
}
return NewPod(j).(Loggable).Logs(ns, pod, co, lines, prev)
return NewPod(j).Logs(ns, pod, co, lines, prev)
}
// Events retrieved jobs events.

View File

@ -9,6 +9,7 @@ import (
type (
// MetricsServer serves cluster metrics for nodes and pods.
MetricsServer struct {
*base
Connection
}
@ -43,7 +44,7 @@ type (
// NewMetricsServer return a metric server instance.
func NewMetricsServer(c Connection) *MetricsServer {
return &MetricsServer{c}
return &MetricsServer{&base{}, c}
}
// NodesMetrics retrieves metrics for a given set of nodes.
@ -51,16 +52,16 @@ func (m *MetricsServer) NodesMetrics(nodes []v1.Node, metrics []mv1beta1.NodeMet
for _, n := range nodes {
mmx[n.Name] = NodeMetrics{
AvailCPU: n.Status.Allocatable.Cpu().MilliValue(),
AvailMEM: asMi(n.Status.Allocatable.Memory().Value()),
AvailMEM: ToMB(n.Status.Allocatable.Memory().Value()),
TotalCPU: n.Status.Capacity.Cpu().MilliValue(),
TotalMEM: asMi(n.Status.Capacity.Memory().Value()),
TotalMEM: ToMB(n.Status.Capacity.Memory().Value()),
}
}
for _, c := range metrics {
if mx, ok := mmx[c.Name]; ok {
mx.CurrentCPU = c.Usage.Cpu().MilliValue()
mx.CurrentMEM = asMi(c.Usage.Memory().Value())
mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
mmx[c.Name] = mx
}
}
@ -73,16 +74,16 @@ func (m *MetricsServer) ClusterLoad(nodes []v1.Node, metrics []mv1beta1.NodeMetr
for _, n := range nodes {
nodeMetrics[n.Name] = NodeMetrics{
AvailCPU: n.Status.Allocatable.Cpu().MilliValue(),
AvailMEM: asMi(n.Status.Allocatable.Memory().Value()),
AvailMEM: ToMB(n.Status.Allocatable.Memory().Value()),
TotalCPU: n.Status.Capacity.Cpu().MilliValue(),
TotalMEM: asMi(n.Status.Capacity.Memory().Value()),
TotalMEM: ToMB(n.Status.Capacity.Memory().Value()),
}
}
for _, mx := range metrics {
if m, ok := nodeMetrics[mx.Name]; ok {
m.CurrentCPU = mx.Usage.Cpu().MilliValue()
m.CurrentMEM = asMi(mx.Usage.Memory().Value())
m.CurrentMEM = ToMB(mx.Usage.Memory().Value())
nodeMetrics[mx.Name] = m
}
}
@ -133,7 +134,7 @@ func (m *MetricsServer) PodsMetrics(pods []mv1beta1.PodMetrics, mmx PodsMetrics)
var mx PodMetrics
for _, c := range p.Containers {
mx.CurrentCPU += c.Usage.Cpu().MilliValue()
mx.CurrentMEM += asMi(c.Usage.Memory().Value())
mx.CurrentMEM += ToMB(c.Usage.Memory().Value())
}
mmx[p.Namespace+"/"+p.Name] = mx
}

View File

@ -6,12 +6,13 @@ import (
// Node represents a Kubernetes node.
type Node struct {
*base
Connection
}
// NewNode returns a new Node.
func NewNode(c Connection) Cruder {
return &Node{c}
func NewNode(c Connection) *Node {
return &Node{&base{}, c}
}
// Get a node.
@ -21,7 +22,11 @@ func (n *Node) Get(_, name string) (interface{}, error) {
// List all nodes on the cluster.
func (n *Node) List(_ string) (Collection, error) {
rr, err := n.DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: n.labelSelector,
FieldSelector: n.fieldSelector,
}
rr, err := n.DialOrDie().CoreV1().Nodes().List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// Namespace represents a Kubernetes namespace.
type Namespace struct {
*base
Connection
}
// NewNamespace returns a new Namespace.
func NewNamespace(c Connection) Cruder {
return &Namespace{c}
func NewNamespace(c Connection) *Namespace {
return &Namespace{&base{}, c}
}
// Get a active namespace.
@ -21,7 +22,11 @@ func (n *Namespace) Get(_, name string) (interface{}, error) {
// List all active namespaces on the cluster.
func (n *Namespace) List(_ string) (Collection, error) {
rr, err := n.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: n.labelSelector,
FieldSelector: n.fieldSelector,
}
rr, err := n.DialOrDie().CoreV1().Namespaces().List(opts)
if err != nil {
return nil, err
}

View File

@ -4,14 +4,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PodDisruptionBudget represents a PodDisruptionBudget Kubernetes resource.
// PodDisruptionBudget represents a Kubernetes PodDisruptionBudget.
type PodDisruptionBudget struct {
*base
Connection
}
// NewPodDisruptionBudget returns a new PodDisruptionBudget.
func NewPodDisruptionBudget(c Connection) Cruder {
return &PodDisruptionBudget{c}
func NewPodDisruptionBudget(c Connection) *PodDisruptionBudget {
return &PodDisruptionBudget{&base{}, c}
}
// Get a pdb.
@ -21,7 +22,11 @@ func (p *PodDisruptionBudget) Get(ns, n string) (interface{}, error) {
// List all pdbs in a given namespace.
func (p *PodDisruptionBudget) List(ns string) (Collection, error) {
rr, err := p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
}
rr, err := p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -8,14 +8,15 @@ import (
const defaultKillGrace int64 = 5
// Pod represents a Kubernetes resource.
// Pod represents a Kubernetes Pod.
type Pod struct {
*base
Connection
}
// NewPod returns a new Pod.
func NewPod(c Connection) Cruder {
return &Pod{c}
func NewPod(c Connection) *Pod {
return &Pod{base: &base{}, Connection: c}
}
// Get a pod.
@ -25,7 +26,13 @@ func (p *Pod) Get(ns, name string) (interface{}, error) {
// List all pods in a given namespace.
func (p *Pod) List(ns string) (Collection, error) {
rr, err := p.DialOrDie().CoreV1().Pods(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
}
// FieldSelector: "spec.nodeName=gke-k9s-default-pool-0fa2fb89-lbtf",
rr, err := p.DialOrDie().CoreV1().Pods(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -4,24 +4,29 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PV represents a Kubernetes PersistentVolume.
type PV struct {
// PeristentVolume represents a Kubernetes PersistentVolume.
type PeristentVolume struct {
*base
Connection
}
// NewPV returns a new PV.
func NewPV(c Connection) Cruder {
return &PV{c}
// NewPersistentVolume returns a new PeristentVolume.
func NewPersistentVolume(c Connection) *PeristentVolume {
return &PeristentVolume{&base{}, c}
}
// Get a PV.
func (p *PV) Get(_, n string) (interface{}, error) {
// Get a PeristentVolume.
func (p *PeristentVolume) Get(_, n string) (interface{}, error) {
return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{})
}
// List all PVs in a given namespace.
func (p *PV) List(_ string) (Collection, error) {
rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(metav1.ListOptions{})
// List all PeristentVolumes in a given namespace.
func (p *PeristentVolume) List(_ string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
}
rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(opts)
if err != nil {
return nil, err
}
@ -34,7 +39,7 @@ func (p *PV) List(_ string) (Collection, error) {
return cc, nil
}
// Delete a PV.
func (p *PV) Delete(_, n string) error {
// Delete a PeristentVolume.
func (p *PeristentVolume) Delete(_, n string) error {
return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil)
}

View File

@ -4,24 +4,29 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PVC represents a Kubernetes service.
type PVC struct {
// PersistentVolumeClaim represents a Kubernetes PersistentVolumeClaim.
type PersistentVolumeClaim struct {
*base
Connection
}
// NewPVC returns a new PVC.
func NewPVC(c Connection) Cruder {
return &PVC{c}
// NewPersistentVolumeClaim returns a new PersistentVolumeClaim.
func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim {
return &PersistentVolumeClaim{&base{}, c}
}
// Get a PVC.
func (p *PVC) Get(ns, n string) (interface{}, error) {
// Get a PersistentVolumeClaim.
func (p *PersistentVolumeClaim) Get(ns, n string) (interface{}, error) {
return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{})
}
// List all PVCs in a given namespace.
func (p *PVC) List(ns string) (Collection, error) {
rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(metav1.ListOptions{})
// List all PersistentVolumeClaims in a given namespace.
func (p *PersistentVolumeClaim) List(ns string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
}
rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts)
if err != nil {
return nil, err
}
@ -33,7 +38,7 @@ func (p *PVC) List(ns string) (Collection, error) {
return cc, nil
}
// Delete a PVC.
func (p *PVC) Delete(ns, n string) error {
// Delete a PersistentVolumeClaim.
func (p *PersistentVolumeClaim) Delete(ns, n string) error {
return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil)
}

View File

@ -4,14 +4,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ReplicationController represents a Kubernetes service.
// ReplicationController represents a Kubernetes ReplicationController.
type ReplicationController struct {
*base
Connection
}
// NewReplicationController returns a new ReplicationController.
func NewReplicationController(c Connection) Cruder {
return &ReplicationController{c}
func NewReplicationController(c Connection) *ReplicationController {
return &ReplicationController{&base{}, c}
}
// Get a RC.
@ -21,7 +22,11 @@ func (r *ReplicationController) Get(ns, n string) (interface{}, error) {
// List all RCs in a given namespace.
func (r *ReplicationController) List(ns string) (Collection, error) {
rr, err := r.DialOrDie().Core().ReplicationControllers(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: r.labelSelector,
FieldSelector: r.fieldSelector,
}
rr, err := r.DialOrDie().Core().ReplicationControllers(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -14,14 +14,15 @@ import (
// Resource represents a Kubernetes Resource
type Resource struct {
*base
Connection
group, version, name string
}
// NewResource returns a new Resource.
func NewResource(c Connection, group, version, name string) Cruder {
return &Resource{Connection: c, group: group, version: version, name: name}
func NewResource(c Connection, group, version, name string) *Resource {
return &Resource{base: &base{}, Connection: c, group: group, version: version, name: name}
}
// GetInfo returns info about apigroup.
@ -29,7 +30,7 @@ func (r *Resource) GetInfo() (string, string, string) {
return r.group, r.version, r.name
}
func (r *Resource) base() dynamic.NamespaceableResourceInterface {
func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface {
g := schema.GroupVersionResource{
Group: r.group,
Version: r.version,
@ -40,7 +41,7 @@ func (r *Resource) base() dynamic.NamespaceableResourceInterface {
// Get a Resource.
func (r *Resource) Get(ns, n string) (interface{}, error) {
return r.base().Namespace(ns).Get(n, metav1.GetOptions{})
return r.nsRes().Namespace(ns).Get(n, metav1.GetOptions{})
}
// List all Resources in a given namespace.
@ -54,7 +55,7 @@ func (r *Resource) List(ns string) (Collection, error) {
// Delete a Resource.
func (r *Resource) Delete(ns, n string) error {
return r.base().Namespace(ns).Delete(n, nil)
return r.nsRes().Namespace(ns).Delete(n, nil)
}
// ----------------------------------------------------------------------------

View File

@ -7,12 +7,13 @@ import (
// Role represents a Kubernetes Role.
type Role struct {
*base
Connection
}
// NewRole returns a new Role.
func NewRole(c Connection) Cruder {
return &Role{c}
func NewRole(c Connection) *Role {
return &Role{&base{}, c}
}
// Get a Role.
@ -22,7 +23,11 @@ func (r *Role) Get(ns, n string) (interface{}, error) {
// List all Roles in a given namespace.
func (r *Role) List(ns string) (Collection, error) {
rr, err := r.DialOrDie().RbacV1().Roles(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: r.labelSelector,
FieldSelector: r.fieldSelector,
}
rr, err := r.DialOrDie().RbacV1().Roles(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -4,12 +4,13 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// RoleBinding represents a Kubernetes RoleBinding.
type RoleBinding struct {
*base
Connection
}
// NewRoleBinding returns a new RoleBinding.
func NewRoleBinding(c Connection) Cruder {
return &RoleBinding{c}
func NewRoleBinding(c Connection) *RoleBinding {
return &RoleBinding{&base{}, c}
}
// Get a RoleBinding.
@ -19,7 +20,11 @@ func (r *RoleBinding) Get(ns, n string) (interface{}, error) {
// List all RoleBindings in a given namespace.
func (r *RoleBinding) List(ns string) (Collection, error) {
rr, err := r.DialOrDie().RbacV1().RoleBindings(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: r.labelSelector,
FieldSelector: r.fieldSelector,
}
rr, err := r.DialOrDie().RbacV1().RoleBindings(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// ReplicaSet represents a Kubernetes ReplicaSet.
type ReplicaSet struct {
*base
Connection
}
// NewReplicaSet returns a new ReplicaSet.
func NewReplicaSet(c Connection) Cruder {
return &ReplicaSet{c}
func NewReplicaSet(c Connection) *ReplicaSet {
return &ReplicaSet{&base{}, c}
}
// Get a ReplicaSet.
@ -21,7 +22,11 @@ func (r *ReplicaSet) Get(ns, n string) (interface{}, error) {
// List all ReplicaSets in a given namespace.
func (r *ReplicaSet) List(ns string) (Collection, error) {
rr, err := r.DialOrDie().Apps().ReplicaSets(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: r.labelSelector,
FieldSelector: r.fieldSelector,
}
rr, err := r.DialOrDie().Apps().ReplicaSets(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// ServiceAccount manages a Kubernetes ServiceAccount.
type ServiceAccount struct {
*base
Connection
}
// NewServiceAccount instantiates a new ServiceAccount.
func NewServiceAccount(c Connection) Cruder {
return &ServiceAccount{c}
func NewServiceAccount(c Connection) *ServiceAccount {
return &ServiceAccount{&base{}, c}
}
// Get a ServiceAccount.
@ -21,7 +22,11 @@ func (s *ServiceAccount) Get(ns, n string) (interface{}, error) {
// List all ServiceAccounts in a given namespace.
func (s *ServiceAccount) List(ns string) (Collection, error) {
rr, err := s.DialOrDie().CoreV1().ServiceAccounts(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: s.labelSelector,
FieldSelector: s.fieldSelector,
}
rr, err := s.DialOrDie().CoreV1().ServiceAccounts(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// Secret represents a Kubernetes Secret.
type Secret struct {
*base
Connection
}
// NewSecret returns a new Secret.
func NewSecret(c Connection) Cruder {
return &Secret{c}
func NewSecret(c Connection) *Secret {
return &Secret{&base{}, c}
}
// Get a Secret.
@ -21,7 +22,11 @@ func (s *Secret) Get(ns, n string) (interface{}, error) {
// List all Secrets in a given namespace.
func (s *Secret) List(ns string) (Collection, error) {
rr, err := s.DialOrDie().CoreV1().Secrets(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: s.labelSelector,
FieldSelector: s.fieldSelector,
}
rr, err := s.DialOrDie().CoreV1().Secrets(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// StatefulSet manages a Kubernetes StatefulSet.
type StatefulSet struct {
*base
Connection
}
// NewStatefulSet instantiates a new StatefulSet.
func NewStatefulSet(c Connection) Cruder {
return &StatefulSet{c}
func NewStatefulSet(c Connection) *StatefulSet {
return &StatefulSet{&base{}, c}
}
// Get a StatefulSet.
@ -21,7 +22,11 @@ func (s *StatefulSet) Get(ns, n string) (interface{}, error) {
// List all StatefulSets in a given namespace.
func (s *StatefulSet) List(ns string) (Collection, error) {
rr, err := s.DialOrDie().AppsV1().StatefulSets(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: s.labelSelector,
FieldSelector: s.fieldSelector,
}
rr, err := s.DialOrDie().AppsV1().StatefulSets(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -6,12 +6,13 @@ import (
// Service represents a Kubernetes Service.
type Service struct {
*base
Connection
}
// NewService returns a new Service.
func NewService(c Connection) Cruder {
return &Service{c}
func NewService(c Connection) *Service {
return &Service{&base{}, c}
}
// Get a service.
@ -21,7 +22,11 @@ func (s *Service) Get(ns, n string) (interface{}, error) {
// List all Services in a given namespace.
func (s *Service) List(ns string) (Collection, error) {
rr, err := s.DialOrDie().CoreV1().Services(ns).List(metav1.ListOptions{})
opts := metav1.ListOptions{
LabelSelector: s.labelSelector,
FieldSelector: s.fieldSelector,
}
rr, err := s.DialOrDie().CoreV1().Services(ns).List(opts)
if err != nil {
return nil, err
}

View File

@ -20,6 +20,8 @@ type (
Get(ns string, name string) (interface{}, error)
List(ns string) (k8s.Collection, error)
Delete(ns string, name string) error
SetLabelSelector(string)
SetFieldSelector(string)
}
// Connection represents a Kubenetes apiserver connection.
@ -45,6 +47,16 @@ func NewBase(c Connection, r Cruder) *Base {
return &Base{Connection: c, Resource: r}
}
// SetFieldSelector refines query results via selector.
func (b *Base) SetFieldSelector(s string) {
b.Resource.SetFieldSelector(s)
}
// SetLabelSelector refines query results via labels.
func (b *Base) SetLabelSelector(s string) {
b.Resource.SetLabelSelector(s)
}
// Name returns the resource namespaced name.
func (b *Base) Name() string {
return b.path

View File

@ -0,0 +1,262 @@
package resource
import (
"bufio"
"context"
"fmt"
"strconv"
"time"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
)
type (
// Container represents a container on a pod.
Container struct {
*Base
pod *v1.Pod
isInit bool
instance v1.Container
MetricsServer MetricsServer
metrics k8s.PodMetrics
}
)
// NewContainerList returns a collection of container.
func NewContainerList(c Connection, mx MetricsServer, pod *v1.Pod) List {
return NewList(
"",
"co",
NewContainer(c, mx, pod),
0,
)
}
// NewContainer returns a new set of containers.
func NewContainer(c Connection, mx MetricsServer, pod *v1.Pod) *Container {
co := Container{
Base: &Base{Connection: c, Resource: k8s.NewPod(c)},
pod: pod,
MetricsServer: mx,
metrics: k8s.PodMetrics{},
}
co.Factory = &co
return &co
}
// New builds a new Container instance from a k8s resource.
func (r *Container) New(i interface{}) Columnar {
co := NewContainer(r.Connection, r.MetricsServer, r.pod)
co.instance = i.(v1.Container)
co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name
return co
}
// Metrics retrieves cpu/mem resource consumption on associated pod.
func (r *Container) Metrics() k8s.PodMetrics {
return r.metrics
}
// SetMetrics set the current k8s resource metrics on associated pod.
func (r *Container) SetMetrics(m k8s.PodMetrics) {
r.metrics = m
}
// Marshal resource to yaml.
func (r *Container) Marshal(path string) (string, error) {
return "", nil
}
// Logs tails a given container logs
func (r *Container) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) {
req := r.Resource.(k8s.Loggable).Logs(ns, n, co, lines, prev)
ctx, cancel := context.WithCancel(context.TODO())
req.Context(ctx)
blocked := true
go func() {
select {
case <-time.After(defaultTimeout):
if blocked {
close(c)
cancel()
}
}
}()
// This call will block if nothing is in the stream!!
stream, err := req.Stream()
blocked = false
if err != nil {
log.Error().Msgf("Tail logs failed `%s/%s:%s -- %v", ns, n, co, err)
return cancel, fmt.Errorf("%v", err)
}
go func() {
defer func() {
stream.Close()
cancel()
close(c)
}()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
c <- scanner.Text()
}
}()
return cancel, nil
}
// List resources for a given namespace.
func (r *Container) List(ns string) (Columnars, error) {
icos := r.pod.Spec.InitContainers
cos := r.pod.Spec.Containers
cc := make(Columnars, 0, len(icos)+len(cos))
for _, co := range icos {
ci := r.New(co)
ci.(*Container).isInit = true
cc = append(cc, ci)
}
for _, co := range cos {
cc = append(cc, r.New(co))
}
return cc, nil
}
// Header return resource header.
func (*Container) Header(ns string) Row {
hh := Row{}
return append(hh,
"NAME",
"IMAGE",
"READY",
"STATE",
"RS",
"LPROB",
"RPROB",
"CPU",
"MEM",
"RCPU",
"RMEM",
"AGE",
)
}
// Fields retrieves displayable fields.
func (r *Container) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
mxs, _ := r.MetricsServer.FetchPodsMetrics(r.pod.Namespace)
var cpu, mem string
for _, mx := range mxs {
if mx.Name != r.pod.Name {
continue
}
for _, co := range mx.Containers {
if co.Name != i.Name {
continue
}
cpu, mem = toRes(co.Usage)
}
}
rcpu, rmem := resources(i)
var cs *v1.ContainerStatus
for _, c := range r.pod.Status.ContainerStatuses {
if c.Name != i.Name {
continue
}
cs = &c
}
if cs == nil {
for _, c := range r.pod.Status.InitContainerStatuses {
if c.Name != i.Name {
continue
}
cs = &c
}
}
return append(ff,
i.Name,
i.Image,
boolToStr(cs.Ready),
toState(cs.State),
strconv.Itoa(int(cs.RestartCount)),
probe(i.LivenessProbe),
probe(i.ReadinessProbe),
cpu,
mem,
rcpu,
rmem,
toAge(r.pod.CreationTimestamp),
)
}
// ----------------------------------------------------------------------------
// Helpers...
func toState(s v1.ContainerState) string {
switch {
case s.Waiting != nil:
if s.Waiting.Reason != "" {
return s.Waiting.Reason
}
return "Waiting"
case s.Terminated != nil:
if s.Terminated.Reason != "" {
return s.Terminated.Reason
}
return "Terminated"
case s.Running != nil:
return "Running"
default:
return MissingValue
}
}
func toRes(r v1.ResourceList) (string, string) {
cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory]
return ToMillicore(cpu.MilliValue()), ToMi(k8s.ToMB(mem.Value()))
}
func resources(c v1.Container) (cpu, mem string) {
req, lim := c.Resources.Requests, c.Resources.Limits
if len(req) == 0 {
if len(lim) != 0 {
return toRes(lim)
}
} else {
return toRes(req)
}
return "0", "0"
}
func probe(p *v1.Probe) string {
if p == nil {
return "no"
}
return "yes"
}
func asMi(v int64) float64 {
const megaByte = 1024 * 1024
return float64(v) / megaByte
}

View File

@ -10,40 +10,40 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// CRD tracks a kubernetes resource.
type CRD struct {
// CustomResourceDefinition tracks a kubernetes resource.
type CustomResourceDefinition struct {
*Base
instance *unstructured.Unstructured
}
// NewCRDList returns a new resource list.
func NewCRDList(c Connection, ns string) List {
// NewCustomResourceDefinitionList returns a new resource list.
func NewCustomResourceDefinitionList(c Connection, ns string) List {
return NewList(
NotNamespaced,
"crd",
NewCRD(c),
NewCustomResourceDefinition(c),
CRUDAccess|DescribeAccess,
)
}
// NewCRD instantiates a new CRD.
func NewCRD(c Connection) *CRD {
crd := &CRD{&Base{Connection: c, Resource: k8s.NewCRD(c)}, nil}
// NewCustomResourceDefinition instantiates a new CustomResourceDefinition.
func NewCustomResourceDefinition(c Connection) *CustomResourceDefinition {
crd := &CustomResourceDefinition{&Base{Connection: c, Resource: k8s.NewCustomResourceDefinition(c)}, nil}
crd.Factory = crd
return crd
}
// New builds a new CRD instance from a k8s resource.
func (r *CRD) New(i interface{}) Columnar {
c := NewCRD(r.Connection)
// New builds a new CustomResourceDefinition instance from a k8s resource.
func (r *CustomResourceDefinition) New(i interface{}) Columnar {
c := NewCustomResourceDefinition(r.Connection)
switch instance := i.(type) {
case *unstructured.Unstructured:
c.instance = instance
case unstructured.Unstructured:
c.instance = &instance
default:
log.Fatal().Msgf("unknown CRD type %#v", i)
log.Fatal().Msgf("unknown CustomResourceDefinition type %#v", i)
}
meta := c.instance.Object["metadata"].(map[string]interface{})
c.path = meta["name"].(string)
@ -52,7 +52,7 @@ func (r *CRD) New(i interface{}) Columnar {
}
// Marshal a resource.
func (r *CRD) Marshal(path string) (string, error) {
func (r *CustomResourceDefinition) Marshal(path string) (string, error) {
ns, n := namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
@ -70,12 +70,12 @@ func (r *CRD) Marshal(path string) (string, error) {
}
// Header return the resource header.
func (*CRD) Header(ns string) Row {
func (*CustomResourceDefinition) Header(ns string) Row {
return Row{"NAME", "AGE"}
}
// Fields retrieves displayable fields.
func (r *CRD) Fields(ns string) Row {
func (r *CustomResourceDefinition) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
@ -89,7 +89,7 @@ func (r *CRD) Fields(ns string) Row {
}
// ExtFields returns extended fields.
func (r *CRD) ExtFields() Properties {
func (r *CustomResourceDefinition) ExtFields() Properties {
var (
pp = Properties{}
i = r.instance

View File

@ -10,12 +10,12 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func NewCRDListWithArgs(ns string, r *resource.CRD) resource.List {
func NewCRDListWithArgs(ns string, r *resource.CustomResourceDefinition) resource.List {
return resource.NewList("-", "crd", r, resource.CRUDAccess|resource.DescribeAccess)
}
func NewCRDWithArgs(conn k8s.Connection, res resource.Cruder) *resource.CRD {
r := &resource.CRD{Base: resource.NewBase(conn, res)}
func NewCRDWithArgs(conn k8s.Connection, res resource.Cruder) *resource.CustomResourceDefinition {
r := &resource.CustomResourceDefinition{Base: resource.NewBase(conn, res)}
r.Factory = r
return r
@ -130,12 +130,12 @@ func k8sCRDFull() *unstructured.Unstructured {
func newCRDFull() resource.Columnar {
mc := NewMockConnection()
return resource.NewCRD(mc).New(k8sCRDFull())
return resource.NewCustomResourceDefinition(mc).New(k8sCRDFull())
}
func newCRD() resource.Columnar {
mc := NewMockConnection()
return resource.NewCRD(mc).New(k8sCRD())
return resource.NewCustomResourceDefinition(mc).New(k8sCRD())
}
func crdYaml() string {

View File

@ -13,12 +13,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewHPAListWithArgs(ns string, r *resource.HPA) resource.List {
func NewHPAListWithArgs(ns string, r *resource.HorizontalPodAutoscaler) resource.List {
return resource.NewList(ns, "hpa", r, resource.AllVerbsAccess|resource.DescribeAccess)
}
func NewHPAWithArgs(conn k8s.Connection, res resource.Cruder) *resource.HPA {
r := &resource.HPA{Base: resource.NewBase(conn, res)}
func NewHPAWithArgs(conn k8s.Connection, res resource.Cruder) *resource.HorizontalPodAutoscaler {
r := &resource.HorizontalPodAutoscaler{Base: resource.NewBase(conn, res)}
r.Factory = r
return r
}
@ -127,7 +127,7 @@ func k8sHPA() *autoscalingv2beta2.HorizontalPodAutoscaler {
func newHPA() resource.Columnar {
mc := NewMockConnection()
return resource.NewHPA(mc).New(k8sHPA())
return resource.NewHorizontalPodAutoscaler(mc).New(k8sHPA())
}
func hpaYaml() string {

View File

@ -8,40 +8,40 @@ import (
autoscalingv1 "k8s.io/api/autoscaling/v1"
)
// HPAV1 tracks a kubernetes resource.
type HPAV1 struct {
// HorizontalPodAutoscalerV1 tracks a kubernetes resource.
type HorizontalPodAutoscalerV1 struct {
*Base
instance *autoscalingv1.HorizontalPodAutoscaler
}
// NewHPAV1List returns a new resource list.
func NewHPAV1List(c Connection, ns string) List {
// NewHorizontalPodAutoscalerV1List returns a new resource list.
func NewHorizontalPodAutoscalerV1List(c Connection, ns string) List {
return NewList(
ns,
"hpa",
NewHPAV1(c),
NewHorizontalPodAutoscalerV1(c),
AllVerbsAccess|DescribeAccess,
)
}
// NewHPAV1 instantiates a new HPAV1.
func NewHPAV1(c Connection) *HPAV1 {
hpa := &HPAV1{&Base{Connection: c, Resource: k8s.NewHPAV1(c)}, nil}
// NewHorizontalPodAutoscalerV1 instantiates a new HorizontalPodAutoscalerV1.
func NewHorizontalPodAutoscalerV1(c Connection) *HorizontalPodAutoscalerV1 {
hpa := &HorizontalPodAutoscalerV1{&Base{Connection: c, Resource: k8s.NewHorizontalPodAutoscalerV1(c)}, nil}
hpa.Factory = hpa
return hpa
}
// New builds a new HPAV1 instance from a k8s resource.
func (r *HPAV1) New(i interface{}) Columnar {
c := NewHPAV1(r.Connection)
// New builds a new HorizontalPodAutoscalerV1 instance from a k8s resource.
func (r *HorizontalPodAutoscalerV1) New(i interface{}) Columnar {
c := NewHorizontalPodAutoscalerV1(r.Connection)
switch instance := i.(type) {
case *autoscalingv1.HorizontalPodAutoscaler:
c.instance = instance
case autoscalingv1.HorizontalPodAutoscaler:
c.instance = &instance
default:
log.Fatal().Msgf("unknown HPAV1 type %#v", i)
log.Fatal().Msgf("unknown HorizontalPodAutoscalerV1 type %#v", i)
}
c.path = c.namespacedName(c.instance.ObjectMeta)
@ -49,7 +49,7 @@ func (r *HPAV1) New(i interface{}) Columnar {
}
// Marshal resource to yaml.
func (r *HPAV1) Marshal(path string) (string, error) {
func (r *HorizontalPodAutoscalerV1) Marshal(path string) (string, error) {
ns, n := namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
@ -64,7 +64,7 @@ func (r *HPAV1) Marshal(path string) (string, error) {
}
// Header return resource header.
func (*HPAV1) Header(ns string) Row {
func (*HorizontalPodAutoscalerV1) Header(ns string) Row {
hh := Row{}
if ns == AllNamespaces {
hh = append(hh, "NAMESPACE")
@ -81,7 +81,7 @@ func (*HPAV1) Header(ns string) Row {
}
// Fields retrieves displayable fields.
func (r *HPAV1) Fields(ns string) Row {
func (r *HorizontalPodAutoscalerV1) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
@ -103,7 +103,7 @@ func (r *HPAV1) Fields(ns string) Row {
// ----------------------------------------------------------------------------
// Helpers...
func (r *HPAV1) toMetrics(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalingv1.HorizontalPodAutoscalerStatus) string {
func (r *HorizontalPodAutoscalerV1) toMetrics(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalingv1.HorizontalPodAutoscalerStatus) string {
current := "<unknown>"
if status.CurrentCPUUtilizationPercentage != nil {
current = strconv.Itoa(int(*status.CurrentCPUUtilizationPercentage)) + "%"

View File

@ -10,40 +10,40 @@ import (
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
)
// HPAV2Beta1 tracks a kubernetes resource.
type HPAV2Beta1 struct {
// HorizontalPodAutoscalerV2Beta1 tracks a kubernetes resource.
type HorizontalPodAutoscalerV2Beta1 struct {
*Base
instance *autoscalingv2beta1.HorizontalPodAutoscaler
}
// NewHPAV2Beta1List returns a new resource list.
func NewHPAV2Beta1List(c Connection, ns string) List {
// NewHorizontalPodAutoscalerV2Beta1List returns a new resource list.
func NewHorizontalPodAutoscalerV2Beta1List(c Connection, ns string) List {
return NewList(
ns,
"hpa",
NewHPAV2Beta1(c),
NewHorizontalPodAutoscalerV2Beta1(c),
AllVerbsAccess|DescribeAccess,
)
}
// NewHPAV2Beta1 instantiates a new HPAV2Beta1.
func NewHPAV2Beta1(c Connection) *HPAV2Beta1 {
hpa := &HPAV2Beta1{&Base{Connection: c, Resource: k8s.NewHPAV2Beta1(c)}, nil}
// NewHorizontalPodAutoscalerV2Beta1 instantiates a new HorizontalPodAutoscalerV2Beta1.
func NewHorizontalPodAutoscalerV2Beta1(c Connection) *HorizontalPodAutoscalerV2Beta1 {
hpa := &HorizontalPodAutoscalerV2Beta1{&Base{Connection: c, Resource: k8s.NewHorizontalPodAutoscalerV2Beta1(c)}, nil}
hpa.Factory = hpa
return hpa
}
// New builds a new HPAV2Beta1 instance from a k8s resource.
func (r *HPAV2Beta1) New(i interface{}) Columnar {
c := NewHPAV2Beta1(r.Connection)
// New builds a new HorizontalPodAutoscalerV2Beta1 instance from a k8s resource.
func (r *HorizontalPodAutoscalerV2Beta1) New(i interface{}) Columnar {
c := NewHorizontalPodAutoscalerV2Beta1(r.Connection)
switch instance := i.(type) {
case *autoscalingv2beta1.HorizontalPodAutoscaler:
c.instance = instance
case autoscalingv2beta1.HorizontalPodAutoscaler:
c.instance = &instance
default:
log.Fatal().Msgf("unknown HPAV2Beta1 type %#v", i)
log.Fatal().Msgf("unknown HorizontalPodAutoscalerV2Beta1 type %#v", i)
}
c.path = c.namespacedName(c.instance.ObjectMeta)
@ -51,7 +51,7 @@ func (r *HPAV2Beta1) New(i interface{}) Columnar {
}
// Marshal resource to yaml.
func (r *HPAV2Beta1) Marshal(path string) (string, error) {
func (r *HorizontalPodAutoscalerV2Beta1) Marshal(path string) (string, error) {
ns, n := namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
@ -66,7 +66,7 @@ func (r *HPAV2Beta1) Marshal(path string) (string, error) {
}
// Header return resource header.
func (*HPAV2Beta1) Header(ns string) Row {
func (*HorizontalPodAutoscalerV2Beta1) Header(ns string) Row {
hh := Row{}
if ns == AllNamespaces {
hh = append(hh, "NAMESPACE")
@ -83,7 +83,7 @@ func (*HPAV2Beta1) Header(ns string) Row {
}
// Fields retrieves displayable fields.
func (r *HPAV2Beta1) Fields(ns string) Row {
func (r *HorizontalPodAutoscalerV2Beta1) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
@ -105,7 +105,7 @@ func (r *HPAV2Beta1) Fields(ns string) Row {
// ----------------------------------------------------------------------------
// Helpers...
func (r *HPAV2Beta1) toMetrics(specs []autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
func (r *HorizontalPodAutoscalerV2Beta1) toMetrics(specs []autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
if len(specs) == 0 {
return "<none>"
}
@ -147,7 +147,7 @@ func (r *HPAV2Beta1) toMetrics(specs []autoscalingv2beta1.MetricSpec, statuses [
return ret
}
func (*HPAV2Beta1) externalMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
func (*HorizontalPodAutoscalerV2Beta1) externalMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
if spec.External.TargetAverageValue != nil {
@ -163,7 +163,7 @@ func (*HPAV2Beta1) externalMetrics(i int, spec autoscalingv2beta1.MetricSpec, st
return fmt.Sprintf("%s/%s", current, spec.External.TargetValue.String())
}
func (*HPAV2Beta1) resourceMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
func (*HorizontalPodAutoscalerV2Beta1) resourceMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
if spec.Resource.TargetAverageValue != nil {

View File

@ -10,40 +10,40 @@ import (
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
)
// HPA tracks a kubernetes resource.
type HPA struct {
// HorizontalPodAutoscaler tracks a kubernetes resource.
type HorizontalPodAutoscaler struct {
*Base
instance *autoscalingv2beta2.HorizontalPodAutoscaler
}
// NewHPAList returns a new resource list.
func NewHPAList(c Connection, ns string) List {
// NewHorizontalPodAutoscalerList returns a new resource list.
func NewHorizontalPodAutoscalerList(c Connection, ns string) List {
return NewList(
ns,
"hpa",
NewHPA(c),
NewHorizontalPodAutoscaler(c),
AllVerbsAccess|DescribeAccess,
)
}
// NewHPA instantiates a new HPA.
func NewHPA(c Connection) *HPA {
hpa := &HPA{&Base{Connection: c, Resource: k8s.NewHPAV2Beta2(c)}, nil}
// NewHorizontalPodAutoscaler instantiates a new HorizontalPodAutoscaler.
func NewHorizontalPodAutoscaler(c Connection) *HorizontalPodAutoscaler {
hpa := &HorizontalPodAutoscaler{&Base{Connection: c, Resource: k8s.NewHorizontalPodAutoscalerV2Beta2(c)}, nil}
hpa.Factory = hpa
return hpa
}
// New builds a new HPA instance from a k8s resource.
func (r *HPA) New(i interface{}) Columnar {
c := NewHPA(r.Connection)
// New builds a new HorizontalPodAutoscaler instance from a k8s resource.
func (r *HorizontalPodAutoscaler) New(i interface{}) Columnar {
c := NewHorizontalPodAutoscaler(r.Connection)
switch instance := i.(type) {
case *autoscalingv2beta2.HorizontalPodAutoscaler:
c.instance = instance
case autoscalingv2beta2.HorizontalPodAutoscaler:
c.instance = &instance
default:
log.Fatal().Msgf("unknown HPA type %#v", i)
log.Fatal().Msgf("unknown HorizontalPodAutoscaler type %#v", i)
}
c.path = c.namespacedName(c.instance.ObjectMeta)
@ -51,7 +51,7 @@ func (r *HPA) New(i interface{}) Columnar {
}
// Marshal resource to yaml.
func (r *HPA) Marshal(path string) (string, error) {
func (r *HorizontalPodAutoscaler) Marshal(path string) (string, error) {
ns, n := namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
@ -66,7 +66,7 @@ func (r *HPA) Marshal(path string) (string, error) {
}
// Header return resource header.
func (*HPA) Header(ns string) Row {
func (*HorizontalPodAutoscaler) Header(ns string) Row {
hh := Row{}
if ns == AllNamespaces {
hh = append(hh, "NAMESPACE")
@ -83,7 +83,7 @@ func (*HPA) Header(ns string) Row {
}
// Fields retrieves displayable fields.
func (r *HPA) Fields(ns string) Row {
func (r *HorizontalPodAutoscaler) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance

View File

@ -65,6 +65,8 @@ type (
Reconcile() error
GetName() string
Access(flag int) bool
SetFieldSelector(string)
SetLabelSelector(string)
}
// Columnar tracks resources that can be diplayed in a tabular fashion.
@ -84,7 +86,7 @@ type (
// Columnars a collection of columnars.
Columnars []Columnar
// Resource tracks generic Kubernetes resources.
// Resource represents a tabular Kubernetes resource.
Resource interface {
New(interface{}) Columnar
Get(path string) (Columnar, error)
@ -93,6 +95,8 @@ type (
Describe(kind, pa string, flags *genericclioptions.ConfigFlags) (string, error)
Marshal(pa string) (string, error)
Header(ns string) Row
SetFieldSelector(string)
SetLabelSelector(string)
}
list struct {
@ -118,6 +122,16 @@ func NewList(ns, name string, res Resource, verbs int) *list {
}
}
// SetFieldSelector narrows down resource query given fields selection.
func (l *list) SetFieldSelector(s string) {
l.resource.SetFieldSelector(s)
}
// SetLabelSelector narrows down resource query via labels selections.
func (l *list) SetLabelSelector(s string) {
l.resource.SetLabelSelector(s)
}
// Access check access control on a given resource.
func (l *list) Access(f int) bool {
return l.verbs&f == f

View File

@ -71,6 +71,22 @@ func (mock *MockCruder) List(_param0 string) (k8s.Collection, error) {
return ret0, ret1
}
func (mock *MockCruder) SetFieldSelector(_param0 string) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockCruder().")
}
params := []pegomock.Param{_param0}
pegomock.GetGenericMockFrom(mock).Invoke("SetFieldSelector", params, []reflect.Type{})
}
func (mock *MockCruder) SetLabelSelector(_param0 string) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockCruder().")
}
params := []pegomock.Param{_param0}
pegomock.GetGenericMockFrom(mock).Invoke("SetLabelSelector", params, []reflect.Type{})
}
func (mock *MockCruder) VerifyWasCalledOnce() *VerifierCruder {
return &VerifierCruder{
mock: mock,
@ -196,3 +212,57 @@ func (c *Cruder_List_OngoingVerification) GetAllCapturedArguments() (_param0 []s
}
return
}
func (verifier *VerifierCruder) SetFieldSelector(_param0 string) *Cruder_SetFieldSelector_OngoingVerification {
params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetFieldSelector", params, verifier.timeout)
return &Cruder_SetFieldSelector_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type Cruder_SetFieldSelector_OngoingVerification struct {
mock *MockCruder
methodInvocations []pegomock.MethodInvocation
}
func (c *Cruder_SetFieldSelector_OngoingVerification) GetCapturedArguments() string {
_param0 := c.GetAllCapturedArguments()
return _param0[len(_param0)-1]
}
func (c *Cruder_SetFieldSelector_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(string)
}
}
return
}
func (verifier *VerifierCruder) SetLabelSelector(_param0 string) *Cruder_SetLabelSelector_OngoingVerification {
params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetLabelSelector", params, verifier.timeout)
return &Cruder_SetLabelSelector_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type Cruder_SetLabelSelector_OngoingVerification struct {
mock *MockCruder
methodInvocations []pegomock.MethodInvocation
}
func (c *Cruder_SetLabelSelector_OngoingVerification) GetCapturedArguments() string {
_param0 := c.GetAllCapturedArguments()
return _param0[len(_param0)-1]
}
func (c *Cruder_SetLabelSelector_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(string)
}
}
return
}

View File

@ -86,6 +86,22 @@ func (mock *MockSwitchableCruder) MustCurrentContextName() string {
return ret0
}
func (mock *MockSwitchableCruder) SetFieldSelector(_param0 string) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockSwitchableCruder().")
}
params := []pegomock.Param{_param0}
pegomock.GetGenericMockFrom(mock).Invoke("SetFieldSelector", params, []reflect.Type{})
}
func (mock *MockSwitchableCruder) SetLabelSelector(_param0 string) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockSwitchableCruder().")
}
params := []pegomock.Param{_param0}
pegomock.GetGenericMockFrom(mock).Invoke("SetLabelSelector", params, []reflect.Type{})
}
func (mock *MockSwitchableCruder) Switch(_param0 string) error {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockSwitchableCruder().")
@ -244,6 +260,60 @@ func (c *SwitchableCruder_MustCurrentContextName_OngoingVerification) GetCapture
func (c *SwitchableCruder_MustCurrentContextName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierSwitchableCruder) SetFieldSelector(_param0 string) *SwitchableCruder_SetFieldSelector_OngoingVerification {
params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetFieldSelector", params, verifier.timeout)
return &SwitchableCruder_SetFieldSelector_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type SwitchableCruder_SetFieldSelector_OngoingVerification struct {
mock *MockSwitchableCruder
methodInvocations []pegomock.MethodInvocation
}
func (c *SwitchableCruder_SetFieldSelector_OngoingVerification) GetCapturedArguments() string {
_param0 := c.GetAllCapturedArguments()
return _param0[len(_param0)-1]
}
func (c *SwitchableCruder_SetFieldSelector_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(string)
}
}
return
}
func (verifier *VerifierSwitchableCruder) SetLabelSelector(_param0 string) *SwitchableCruder_SetLabelSelector_OngoingVerification {
params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetLabelSelector", params, verifier.timeout)
return &SwitchableCruder_SetLabelSelector_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type SwitchableCruder_SetLabelSelector_OngoingVerification struct {
mock *MockSwitchableCruder
methodInvocations []pegomock.MethodInvocation
}
func (c *SwitchableCruder_SetLabelSelector_OngoingVerification) GetCapturedArguments() string {
_param0 := c.GetAllCapturedArguments()
return _param0[len(_param0)-1]
}
func (c *SwitchableCruder_SetLabelSelector_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(string)
}
}
return
}
func (verifier *VerifierSwitchableCruder) Switch(_param0 string) *SwitchableCruder_Switch_OngoingVerification {
params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Switch", params, verifier.timeout)

View File

@ -107,14 +107,11 @@ func (*Node) Header(ns string) Row {
return Row{
"NAME",
"STATUS",
"ROLE",
"VERSION",
"KERNEL",
"INTERNAL-IP",
"EXTERNAL-IP",
"CPU%",
"MEM%",
"RCPU%",
"RMEM%",
"CPU",
"MEM",
"RCPU",
@ -143,40 +140,59 @@ func (r *Node) Fields(ns string) Row {
rcpu, rmem := reqs["cpu"], reqs["memory"]
var pcpur float64
if r.metrics.AvailCPU > 0 {
pcpur = toPerc(float64(rcpu.MilliValue()), float64(r.metrics.AvailCPU))
}
var pmemr float64
if r.metrics.AvailMEM > 0 {
pmemr = toPerc(float64(rmem.Value()/(1024*1024)), float64(r.metrics.AvailMEM))
}
pcpur := toPerc(float64(rcpu.MilliValue()), float64(r.metrics.AvailCPU))
pmemr := toPerc(k8s.ToMB(rmem.Value()), float64(r.metrics.AvailMEM))
return append(ff,
i.Name,
r.status(i),
r.nodeRoles(i),
i.Status.NodeInfo.KubeletVersion,
i.Status.NodeInfo.KernelVersion,
iIP,
eIP,
asPerc(toPerc(float64(r.metrics.CurrentCPU), float64(r.metrics.AvailCPU))),
asPerc(toPerc(r.metrics.CurrentMEM, r.metrics.AvailMEM)),
asPerc(pcpur),
asPerc(pmemr),
ToMillicore(r.metrics.CurrentCPU),
ToMi(r.metrics.CurrentMEM),
withPerc(ToMillicore(r.metrics.CurrentCPU), asPerc(toPerc(float64(r.metrics.CurrentCPU), float64(r.metrics.AvailCPU)))),
withPerc(ToMi(r.metrics.CurrentMEM), asPerc(toPerc(r.metrics.CurrentMEM, r.metrics.AvailMEM))),
withPerc(rcpu.String(), asPerc(pcpur)),
withPerc(rmem.String(), asPerc(pmemr)),
ToMillicore(r.metrics.AvailCPU),
ToMi(r.metrics.AvailMEM),
rcpu.String(),
rmem.String(),
toAge(i.ObjectMeta.CreationTimestamp),
)
}
func withPerc(v, p string) string {
return v + " (" + p + ")"
}
// ----------------------------------------------------------------------------
// Helpers...
func (*Node) nodeRoles(node *v1.Node) string {
const (
labelNodeRolePrefix = "node-role.kubernetes.io/"
nodeLabelRole = "kubernetes.io/role"
)
roles := sets.NewString()
for k, v := range node.Labels {
switch {
case strings.HasPrefix(k, labelNodeRolePrefix):
if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 {
roles.Insert(role)
}
case k == nodeLabelRole && v != "":
roles.Insert(v)
}
}
if len(roles) == 0 {
return MissingValue
}
return strings.Join(roles.List(), ",")
}
func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) {
for _, a := range addrs {
switch a.Type {

View File

@ -81,7 +81,7 @@ func TestNodeListData(t *testing.T) {
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
row, ok := td.Rows["fred"]
assert.True(t, ok)
assert.Equal(t, 17, len(row.Deltas))
assert.Equal(t, 14, len(row.Deltas))
for _, d := range row.Deltas {
assert.Equal(t, "", d)
}

View File

@ -17,8 +17,8 @@ const (
)
type (
// Container represents a resource that encompass multiple containers.
Container interface {
// Containers represents a resource that supports containers.
Containers interface {
Containers(path string, includeInit bool) ([]string, error)
}

View File

@ -9,40 +9,40 @@ import (
v1 "k8s.io/api/core/v1"
)
// PV tracks a kubernetes resource.
type PV struct {
// PersistentVolume tracks a kubernetes resource.
type PersistentVolume struct {
*Base
instance *v1.PersistentVolume
}
// NewPVList returns a new resource list.
func NewPVList(c Connection, ns string) List {
// NewPersistentVolumeList returns a new resource list.
func NewPersistentVolumeList(c Connection, ns string) List {
return NewList(
NotNamespaced,
"pv",
NewPV(c),
NewPersistentVolume(c),
CRUDAccess|DescribeAccess,
)
}
// NewPV instantiates a new PV.
func NewPV(c Connection) *PV {
p := &PV{&Base{Connection: c, Resource: k8s.NewPV(c)}, nil}
// NewPersistentVolume instantiates a new PersistentVolume.
func NewPersistentVolume(c Connection) *PersistentVolume {
p := &PersistentVolume{&Base{Connection: c, Resource: k8s.NewPersistentVolume(c)}, nil}
p.Factory = p
return p
}
// New builds a new PV instance from a k8s resource.
func (r *PV) New(i interface{}) Columnar {
c := NewPV(r.Connection)
// New builds a new PersistentVolume instance from a k8s resource.
func (r *PersistentVolume) New(i interface{}) Columnar {
c := NewPersistentVolume(r.Connection)
switch instance := i.(type) {
case *v1.PersistentVolume:
c.instance = instance
case v1.PersistentVolume:
c.instance = &instance
default:
log.Fatal().Msgf("unknown PV type %#v", i)
log.Fatal().Msgf("unknown PersistentVolume type %#v", i)
}
c.path = c.namespacedName(c.instance.ObjectMeta)
@ -50,7 +50,7 @@ func (r *PV) New(i interface{}) Columnar {
}
// Marshal resource to yaml.
func (r *PV) Marshal(path string) (string, error) {
func (r *PersistentVolume) Marshal(path string) (string, error) {
ns, n := namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
@ -65,7 +65,7 @@ func (r *PV) Marshal(path string) (string, error) {
}
// Header return resource header.
func (*PV) Header(ns string) Row {
func (*PersistentVolume) Header(ns string) Row {
hh := Row{}
if ns == AllNamespaces {
hh = append(hh, "NAMESPACE")
@ -75,7 +75,7 @@ func (*PV) Header(ns string) Row {
}
// Fields retrieves displayable fields.
func (r *PV) Fields(ns string) Row {
func (r *PersistentVolume) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
if ns == AllNamespaces {
@ -115,7 +115,7 @@ func (r *PV) Fields(ns string) Row {
// ----------------------------------------------------------------------------
// Helpers...
func (r *PV) accessMode(aa []v1.PersistentVolumeAccessMode) string {
func (r *PersistentVolume) accessMode(aa []v1.PersistentVolumeAccessMode) string {
dd := r.accessDedup(aa)
s := make([]string, 0, len(dd))
for i := 0; i < len(aa); i++ {
@ -132,7 +132,7 @@ func (r *PV) accessMode(aa []v1.PersistentVolumeAccessMode) string {
return strings.Join(s, ",")
}
func (r *PV) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentVolumeAccessMode) bool {
func (r *PersistentVolume) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentVolumeAccessMode) bool {
for _, c := range cc {
if c == a {
return true
@ -142,7 +142,7 @@ func (r *PV) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentV
return false
}
func (r *PV) accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
func (r *PersistentVolume) accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
set := []v1.PersistentVolumeAccessMode{}
for _, c := range cc {
if !r.accessContains(set, c) {

View File

@ -11,12 +11,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewPVListWithArgs(ns string, r *resource.PV) resource.List {
func NewPVListWithArgs(ns string, r *resource.PersistentVolume) resource.List {
return resource.NewList(resource.NotNamespaced, "pv", r, resource.CRUDAccess|resource.DescribeAccess)
}
func NewPVWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PV {
r := &resource.PV{Base: resource.NewBase(conn, res)}
func NewPVWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PersistentVolume {
r := &resource.PersistentVolume{Base: resource.NewBase(conn, res)}
r.Factory = r
return r
}
@ -92,7 +92,7 @@ func k8sPV() *v1.PersistentVolume {
func newPV() resource.Columnar {
mc := NewMockConnection()
return resource.NewPV(mc).New(k8sPV())
return resource.NewPersistentVolume(mc).New(k8sPV())
}
func pvYaml() string {

View File

@ -6,40 +6,40 @@ import (
v1 "k8s.io/api/core/v1"
)
// PVC tracks a kubernetes resource.
type PVC struct {
// PersistentVolumeClaim tracks a kubernetes resource.
type PersistentVolumeClaim struct {
*Base
instance *v1.PersistentVolumeClaim
}
// NewPVCList returns a new resource list.
func NewPVCList(c Connection, ns string) List {
// NewPersistentVolumeClaimList returns a new resource list.
func NewPersistentVolumeClaimList(c Connection, ns string) List {
return NewList(
ns,
"pvc",
NewPVC(c),
NewPersistentVolumeClaim(c),
AllVerbsAccess|DescribeAccess,
)
}
// NewPVC instantiates a new PVC.
func NewPVC(c Connection) *PVC {
p := &PVC{&Base{Connection: c, Resource: k8s.NewPVC(c)}, nil}
// NewPersistentVolumeClaim instantiates a new PersistentVolumeClaim.
func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim {
p := &PersistentVolumeClaim{&Base{Connection: c, Resource: k8s.NewPersistentVolumeClaim(c)}, nil}
p.Factory = p
return p
}
// New builds a new PVC instance from a k8s resource.
func (r *PVC) New(i interface{}) Columnar {
c := NewPVC(r.Connection)
// New builds a new PersistentVolumeClaim instance from a k8s resource.
func (r *PersistentVolumeClaim) New(i interface{}) Columnar {
c := NewPersistentVolumeClaim(r.Connection)
switch instance := i.(type) {
case *v1.PersistentVolumeClaim:
c.instance = instance
case v1.PersistentVolumeClaim:
c.instance = &instance
default:
log.Fatal().Msgf("unknown PVC type %#v", i)
log.Fatal().Msgf("unknown PersistentVolumeClaim type %#v", i)
}
c.path = c.namespacedName(c.instance.ObjectMeta)
@ -47,7 +47,7 @@ func (r *PVC) New(i interface{}) Columnar {
}
// Marshal resource to yaml.
func (r *PVC) Marshal(path string) (string, error) {
func (r *PersistentVolumeClaim) Marshal(path string) (string, error) {
ns, n := namespaced(path)
i, err := r.Resource.Get(ns, n)
if err != nil {
@ -62,7 +62,7 @@ func (r *PVC) Marshal(path string) (string, error) {
}
// Header return resource header.
func (*PVC) Header(ns string) Row {
func (*PersistentVolumeClaim) Header(ns string) Row {
hh := Row{}
if ns == AllNamespaces {
hh = append(hh, "NAMESPACE")
@ -72,7 +72,7 @@ func (*PVC) Header(ns string) Row {
}
// Fields retrieves displayable fields.
func (r *PVC) Fields(ns string) Row {
func (r *PersistentVolumeClaim) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
if ns == AllNamespaces {
@ -84,7 +84,7 @@ func (r *PVC) Fields(ns string) Row {
phase = "Terminating"
}
pv := PV{}
var pv PersistentVolume
storage := i.Spec.Resources.Requests[v1.ResourceStorage]
var capacity, accessModes string
if i.Spec.VolumeName != "" {

View File

@ -12,12 +12,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewPVCListWithArgs(ns string, r *resource.PVC) resource.List {
func NewPVCListWithArgs(ns string, r *resource.PersistentVolumeClaim) resource.List {
return resource.NewList(ns, "pvc", r, resource.AllVerbsAccess|resource.DescribeAccess)
}
func NewPVCWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PVC {
r := &resource.PVC{Base: resource.NewBase(conn, res)}
func NewPVCWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PersistentVolumeClaim {
r := &resource.PersistentVolumeClaim{Base: resource.NewBase(conn, res)}
r.Factory = r
return r
}
@ -100,7 +100,7 @@ func k8sPVC() *v1.PersistentVolumeClaim {
func newPVC() resource.Columnar {
mc := NewMockConnection()
return resource.NewPVC(mc).New(k8sPVC())
return resource.NewPersistentVolumeClaim(mc).New(k8sPVC())
}
func pvcYaml() string {

View File

@ -264,7 +264,7 @@ func (a *appView) showPage(p string) {
a.pages.SwitchToPage(p)
}
func (a *appView) inject(p igniter) {
func (a *appView) inject(i igniter) {
if a.cancel != nil {
a.cancel()
}
@ -273,14 +273,14 @@ func (a *appView) inject(p igniter) {
var ctx context.Context
{
ctx, a.cancel = context.WithCancel(context.Background())
p.init(ctx, a.config.ActiveNamespace())
i.init(ctx, a.config.ActiveNamespace())
}
a.content.AddPage("main", p, true, true)
a.content.AddPage("main", i, true, true)
a.focusGroup = append([]tview.Primitive{}, p)
a.focusGroup = append([]tview.Primitive{}, i)
a.focusCurrent = 0
a.fireFocusChanged(p)
a.SetFocus(p)
a.fireFocusChanged(i)
a.SetFocus(i)
}
func (a *appView) cmdMode() bool {

View File

@ -62,9 +62,6 @@ func (c *command) run(cmd string) bool {
case cmd == "alias":
c.app.inject(newAliasView(c.app))
return true
case cmd == "popeye":
c.app.inject(newPopeyeView(c.app))
return true
case policyMatcher.MatchString(cmd):
tokens := policyMatcher.FindAllStringSubmatch(cmd, -1)
if len(tokens) == 1 && len(tokens[0]) == 3 {

123
internal/views/container.go Normal file
View File

@ -0,0 +1,123 @@
package views
import (
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
type containerView struct {
*resourceView
current igniter
path string
}
func newContainerView(t string, app *appView, list resource.List, path string) resourceViewer {
v := containerView{resourceView: newResourceView(t, app, list).(*resourceView)}
{
v.path = path
v.extraActionsFn = v.extraActions
v.current = app.content.GetPrimitive("main").(igniter)
}
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
v.switchPage("co")
return &v
}
// Protocol...
func (v *containerView) backFn() actionHandler {
return v.backCmd
}
func (v *containerView) appView() *appView {
return v.app
}
func (v *containerView) getList() resource.List {
return v.list
}
func (v *containerView) getSelection() string {
return v.path
}
// Handlers...
func (v *containerView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
v.showLogs(v.selectedItem, v.list.GetName(), v, false)
return nil
}
func (v *containerView) prevLogsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
v.showLogs(v.selectedItem, v.list.GetName(), v, true)
return nil
}
func (v *containerView) showLogs(co, view string, parent loggable, prev bool) {
l := v.GetPrimitive("logs").(*logsView)
l.reload(co, parent, view, prev)
v.switchPage("logs")
}
func (v *containerView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
log.Debug().Msgf("Selected %s", v.selectedItem)
v.shellIn(v.path, v.selectedItem)
return nil
return evt
}
func (v *containerView) shellIn(path, co string) {
ns, po := namespaced(path)
args := make([]string, 0, 12)
args = append(args, "exec", "-it")
args = append(args, "--context", v.app.config.K9s.CurrentContext)
args = append(args, "-n", ns)
args = append(args, po)
if len(co) != 0 {
args = append(args, "-c", co)
}
args = append(args, "--", "sh")
log.Debug().Msgf("Shell args %v", args)
runK(v.app, args...)
}
func (v *containerView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
aa[KeyShiftL] = newKeyAction("Previous Logs", v.prevLogsCmd, true)
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
aa[tcell.KeyEscape] = newKeyAction("Back", v.backCmd, false)
aa[KeyP] = newKeyAction("Previous", v.backCmd, false)
aa[tcell.KeyEnter] = newKeyAction("Enter", v.logsCmd, false)
}
func (v *containerView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
return func(evt *tcell.EventKey) *tcell.EventKey {
t := v.getTV()
t.sortCol.index, t.sortCol.asc = t.nameColIndex()+col, asc
t.refresh()
return nil
}
}
func (v *containerView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.app.inject(v.current)
return nil
}

View File

@ -70,6 +70,7 @@ func (v *helpView) init(_ context.Context, _ string) {
{"tab", "Next term match"},
{"backtab", "Previous term match"},
{"Ctrl-r", "Refresh"},
{"Shift-i", "Invert Sort"},
{"p", "Previous resource view"},
{"q", "Quit"},
}

View File

@ -11,7 +11,7 @@ type jobView struct {
}
func newJobView(t string, app *appView, list resource.List) resourceViewer {
v := jobView{newResourceView(t, app, list).(*resourceView)}
v := jobView{resourceView: newResourceView(t, app, list).(*resourceView)}
{
v.extraActionsFn = v.extraActions
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
@ -50,42 +50,53 @@ func (v *jobView) getSelection() string {
// Handlers...
func (v *jobView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.viewLogs(false) {
return nil
}
return evt
}
func (v *jobView) prevLogsCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.viewLogs(true) {
return nil
}
return evt
}
func (v *jobView) viewLogs(previous bool) bool {
if !v.rowSelected() {
return evt
return false
}
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error().Err(err).Msgf("Unable to fetch containers for %s", v.selectedItem)
return evt
return false
}
if len(cc) == 1 {
v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v)
return nil
v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v, previous)
return true
}
picker := v.GetPrimitive("picker").(*selectList)
picker.populate(cc)
picker.SetSelectedFunc(func(i int, t, d string, r rune) {
v.showLogs(v.selectedItem, t, "picker", picker)
v.showLogs(v.selectedItem, t, "picker", picker, previous)
})
v.switchPage("picker")
return nil
return true
}
func (v *jobView) showLogs(path, co, view string, parent loggable) {
func (v *jobView) showLogs(path, co, view string, parent loggable, prev bool) {
l := v.GetPrimitive("logs").(*logsView)
l.parent = parent
l.parentView = view
l.deleteAllPages()
l.addContainer(co)
l.reload(co, parent, view, prev)
v.switchPage("logs")
l.init()
}
func (v *jobView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
aa[KeyShiftL] = newKeyAction("Previous Logs", v.prevLogsCmd, true)
}

View File

@ -21,12 +21,13 @@ const (
type logsView struct {
*tview.Pages
parentView string
parent loggable
containers []string
actions keyActions
cancelFunc context.CancelFunc
autoScroll bool
parentView string
parent loggable
containers []string
actions keyActions
cancelFunc context.CancelFunc
autoScroll bool
showPrevious bool
}
func newLogsView(pview string, parent loggable) *logsView {
@ -53,7 +54,10 @@ func newLogsView(pview string, parent loggable) *logsView {
// Protocol...
func (v *logsView) init() {
func (v *logsView) reload(co string, parent loggable, view string, prevLogs bool) {
v.parent, v.parentView, v.showPrevious = parent, view, prevLogs
v.deleteAllPages()
v.addContainer(co)
v.load(0)
}
@ -171,7 +175,7 @@ func (v *logsView) doLoad(path, co string) error {
return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource)
}
maxBuff := int64(v.parent.appView().config.K9s.LogRequestSize)
cancelFn, err := res.Logs(c, ns, po, co, maxBuff, false)
cancelFn, err := res.Logs(c, ns, po, co, maxBuff, v.showPrevious)
if err != nil {
cancelFn()
return err

View File

@ -20,12 +20,8 @@ func newNodeView(t string, app *appView, list resource.List) resourceViewer {
}
func (v *nodeView) extraActions(aa keyActions) {
aa[KeyShiftQ] = newKeyAction("Sort CPU%", v.sortColCmd(6, false), true)
aa[KeyShiftW] = newKeyAction("Sort MEM%", v.sortColCmd(7, false), true)
aa[KeyShiftA] = newKeyAction("Sort RCPU%", v.sortColCmd(8, false), true)
aa[KeyShiftS] = newKeyAction("Sort RMEM%", v.sortColCmd(9, false), true)
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(10, false), true)
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(11, false), true)
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(7, false), true)
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(8, false), true)
}
func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -1,11 +1,17 @@
package views
import (
"fmt"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const fmat = "[aqua::b]%s([fuchsia::b]%s[aqua::-])"
type podView struct {
*resourceView
}
@ -22,6 +28,7 @@ func newPodView(t string, app *appView, list resource.List) resourceViewer {
v := podView{newResourceView(t, app, list).(*resourceView)}
{
v.extraActionsFn = v.extraActions
v.enterFn = v.listContainers
}
picker := newSelectList(&v)
@ -38,6 +45,20 @@ func newPodView(t string, app *appView, list resource.List) resourceViewer {
return &v
}
func (v *podView) listContainers(app *appView, _, res, sel string) {
ns, n := namespaced(sel)
po, err := app.conn().DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{})
if err != nil {
log.Error().Err(err).Msgf("Unable to retrieve pod %s", sel)
app.flash(flashErr, err.Error())
return
}
mx := k8s.NewMetricsServer(app.conn())
list := resource.NewContainerList(app.conn(), mx, po)
app.inject(newContainerView(fmt.Sprintf(fmat, "Containers", sel), app, list, namespacedName(po.Namespace, po.Name)))
}
// Protocol...
func (v *podView) backFn() actionHandler {
@ -59,40 +80,51 @@ func (v *podView) getSelection() string {
// Handlers...
func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.viewLogs(false) {
return nil
}
return evt
}
func (v *podView) prevLogsCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.viewLogs(true) {
return nil
}
return evt
}
func (v *podView) viewLogs(prev bool) bool {
if !v.rowSelected() {
return evt
return false
}
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error().Err(err)
return evt
return false
}
if len(cc) == 1 {
v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v)
return nil
v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v, prev)
return true
}
picker := v.GetPrimitive("picker").(*selectList)
picker.populate(cc)
picker.SetSelectedFunc(func(i int, t, d string, r rune) {
v.showLogs(v.selectedItem, t, "picker", picker)
v.showLogs(v.selectedItem, t, "picker", picker, prev)
})
v.switchPage("picker")
return nil
return true
}
func (v *podView) showLogs(path, co, view string, parent loggable) {
func (v *podView) showLogs(path, co, view string, parent loggable, prev bool) {
l := v.GetPrimitive("logs").(*logsView)
l.parent = parent
l.parentView = view
l.deleteAllPages()
l.addContainer(co)
l.reload(co, parent, view, prev)
v.switchPage("logs")
l.init()
}
func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
@ -139,6 +171,7 @@ func (v *podView) shellIn(path, co string) {
func (v *podView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
aa[KeyShiftL] = newKeyAction("Logs", v.prevLogsCmd, true)
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
aa[KeyShiftR] = newKeyAction("Sort Ready", v.sortColCmd(1, false), true)
aa[KeyShiftS] = newKeyAction("Sort Status", v.sortColCmd(2, true), true)
@ -146,7 +179,6 @@ func (v *podView) extraActions(aa keyActions) {
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(4, false), true)
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(5, false), true)
aa[KeyShiftO] = newKeyAction("Sort Node", v.sortColCmd(7, true), true)
aa[KeyShiftQ] = newKeyAction("Sort QOS", v.sortColCmd(8, true), true)
}
func (v *podView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -163,5 +195,5 @@ func fetchContainers(l resource.List, po string, includeInit bool) ([]string, er
if len(po) == 0 {
return []string{}, nil
}
return l.Resource().(resource.Container).Containers(po, includeInit)
return l.Resource().(resource.Containers).Containers(po, includeInit)
}

View File

@ -1,77 +1,77 @@
package views
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
// import (
// "context"
// "fmt"
// "io"
// "os"
// "path/filepath"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/popeye/pkg"
cfg "github.com/derailed/popeye/pkg/config"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
// "github.com/derailed/k9s/internal/config"
// "github.com/derailed/popeye/pkg"
// cfg "github.com/derailed/popeye/pkg/config"
// "github.com/derailed/tview"
// "github.com/gdamore/tcell"
// "github.com/rs/zerolog/log"
// )
type popeyeView struct {
*detailsView
// type popeyeView struct {
// *detailsView
current igniter
ansiWriter io.Writer
}
// current igniter
// ansiWriter io.Writer
// }
func newPopeyeView(app *appView) *popeyeView {
v := popeyeView{}
{
v.detailsView = newDetailsView(app, v.backCmd)
v.SetBorderPadding(0, 0, 1, 1)
v.current = app.content.GetPrimitive("main").(igniter)
v.SetDynamicColors(true)
v.SetWrap(true)
v.setTitle("Popeye")
v.ansiWriter = tview.ANSIWriter(v)
}
v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
// func newPopeyeView(app *appView) *popeyeView {
// v := popeyeView{}
// {
// v.detailsView = newDetailsView(app, v.backCmd)
// v.SetBorderPadding(0, 0, 1, 1)
// v.current = app.content.GetPrimitive("main").(igniter)
// v.SetDynamicColors(true)
// v.SetWrap(true)
// v.setTitle("Popeye")
// v.ansiWriter = tview.ANSIWriter(v)
// }
// v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
return &v
}
// return &v
// }
func (v *popeyeView) init(ctx context.Context, ns string) {
defer func() {
if err := recover(); err != nil {
v.app.flash(flashErr, fmt.Sprintf("%v", err))
}
}()
// func (v *popeyeView) init(ctx context.Context, ns string) {
// defer func() {
// if err := recover(); err != nil {
// v.app.flash(flashErr, fmt.Sprintf("%v", err))
// }
// }()
c := cfg.New()
// c := cfg.New()
spinach := filepath.Join(config.K9sHome, "spinach.yml")
// spinach := filepath.Join(config.K9sHome, "spinach.yml")
if _, err := os.Stat(spinach); err == nil {
c.Spinach = spinach
}
// if _, err := os.Stat(spinach); err == nil {
// c.Spinach = spinach
// }
if v.app.config.K9s.CurrentContext != "" {
v.app.flags.Context = &v.app.config.K9s.CurrentContext
}
// if v.app.config.K9s.CurrentContext != "" {
// v.app.flags.Context = &v.app.config.K9s.CurrentContext
// }
if err := c.Init(v.app.flags); err != nil {
log.Error().Err(err).Msg("Unable to load spinach config")
}
// if err := c.Init(v.app.flags); err != nil {
// log.Error().Err(err).Msg("Unable to load spinach config")
// }
p := pkg.NewPopeye(c, &log.Logger, v.ansiWriter)
p.Sanitize(false)
}
// p := pkg.NewPopeye(c, &log.Logger, v.ansiWriter)
// p.Sanitize(false)
// }
func (v *popeyeView) getTitle() string {
return "Popeye"
}
// func (v *popeyeView) getTitle() string {
// return "Popeye"
// }
func (v *popeyeView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.app.command.previousCmd()
v.app.inject(v.current)
// func (v *popeyeView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
// v.app.command.previousCmd()
// v.app.inject(v.current)
return nil
}
// return nil
// }

View File

@ -44,8 +44,7 @@ func helpCmds(c k8s.Connection) map[string]resCmd {
func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
m := map[string]k8s.APIGroup{}
crds, _ := resource.
NewCRDList(c, resource.AllNamespaces).
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
Resource().
List(resource.AllNamespaces)
@ -136,7 +135,7 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
title: "CustomResourceDefinitions",
api: "apiextensions.k8s.io",
viewFn: newResourceView,
listFn: resource.NewCRDList,
listFn: resource.NewCustomResourceDefinitionList,
},
"cj": {
title: "CronJobs",
@ -222,14 +221,14 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
title: "PersistentVolumes",
api: "",
viewFn: newResourceView,
listFn: resource.NewPVList,
listFn: resource.NewPersistentVolumeList,
colorerFn: pvColorer,
},
"pvc": {
title: "PersistentVolumeClaims",
api: "",
viewFn: newResourceView,
listFn: resource.NewPVCList,
listFn: resource.NewPersistentVolumeClaimList,
colorerFn: pvcColorer,
},
"rb": {
@ -310,7 +309,7 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
title: "HorizontalPodAutoscalers",
api: "autoscaling",
viewFn: newResourceView,
listFn: resource.NewHPAV1List,
listFn: resource.NewHorizontalPodAutoscalerV1List,
}
case "v2beta1":
log.Debug().Msg("Using HPA V2Beta1!")
@ -318,7 +317,7 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
title: "HorizontalPodAutoscalers",
api: "autoscaling",
viewFn: newResourceView,
listFn: resource.NewHPAV2Beta1List,
listFn: resource.NewHorizontalPodAutoscalerV2Beta1List,
}
case "v2beta2":
log.Debug().Msg("Using HPA V2Beta2!")
@ -326,7 +325,7 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
title: "HorizontalPodAutoscalers",
api: "autoscaling",
viewFn: newResourceView,
listFn: resource.NewHPAList,
listFn: resource.NewHorizontalPodAutoscalerList,
}
default:
log.Panic().Msgf("K9s does not currently support HPA version `%s`", rev)

View File

@ -3,11 +3,11 @@ package views
import (
"regexp"
"strconv"
"strings"
"time"
"github.com/derailed/k9s/internal/resource"
res "k8s.io/apimachinery/pkg/api/resource"
"vbom.ml/util/sortorder"
)
type rowSorter struct {
@ -63,11 +63,11 @@ func less(asc bool, c1, c2 string) bool {
return o
}
c := strings.Compare(c1, c2)
b := sortorder.NaturalLess(c1, c2)
if asc {
return c < 0
return b
}
return c > 0
return !b
}
func isDurationSort(asc bool, c1, c2 string) (bool, bool) {
@ -118,18 +118,6 @@ func isMetric(s string) (string, bool) {
}
func isDuration(s string) (time.Duration, bool) {
// rx := regexp.MustCompile(`(\d+)([h|d])`)
// mm := rx.FindStringSubmatch(s)
// if len(mm) == 3 {
// n, _ := strconv.Atoi(mm[1])
// switch mm[2] {
// case "d":
// s = fmt.Sprintf("%dh", n*24)
// case "y":
// s = fmt.Sprintf("%dh", n*365*24)
// }
// }
d, err := time.ParseDuration(s)
if err != nil {
return d, false

View File

@ -29,6 +29,8 @@ func TestGroupSort(t *testing.T) {
{true, []string{"95d", "93d"}, []string{"93d", "95d"}},
{true, []string{"1h10m", "59m"}, []string{"59m", "1h10m"}},
{true, []string{"95m", "1h30m"}, []string{"1h30m", "95m"}},
{true, []string{"b-21", "b-2"}, []string{"b-2", "b-21"}},
{false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}},
}
for _, u := range uu {

View File

@ -74,7 +74,7 @@ func newTableView(app *appView, title string) *tableView {
}
func (v *tableView) bindKeys() {
v.actions[KeyShiftI] = newKeyAction("Invert", v.sortInvertCmd, true)
v.actions[KeyShiftI] = newKeyAction("Invert", v.sortInvertCmd, false)
v.actions[KeyShiftN] = newKeyAction("Sort Name", v.sortColCmd(0), true)
v.actions[KeyShiftA] = newKeyAction("Sort Age", v.sortColCmd(-1), true)