Added cronjob triggers. Tx dzoeteman! + describe support + job logger + bugs

mine
derailed 2019-02-19 18:12:58 -07:00
parent 787f9ff15c
commit 823169ce79
48 changed files with 658 additions and 115 deletions

View File

@ -9,7 +9,7 @@ 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 file an issue please help me verify and close.
If you've filed an issue please help me verify and close.
Thank you so much for your support!!

View File

@ -0,0 +1,33 @@
# Release v0.1.6
<br/>
---
## 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!!
<br/>
---
## Change Logs
<br/>
+ [Feature request #43](https://github.com/derailed/k9s/issues/43) Add CronJob Manual Trigger.
All of this work is attributed to [dzoeteman](https://github.com/dzoeteman). Thank you!
+ Added ability to view logs on Job resource.
+ [Feature request #37](https://github.com/derailed/k9s/issues/37) Added Describe on resources as
in kubectl describe xxx
+ NOTE! Changed alias to `job` and `cron` vs `jo` and `cjo`
---
## Resolved Bugs
- Fix issue with ServiceAccounts not displaying

1
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/gdamore/tcell v1.1.0
github.com/go-openapi/spec v0.18.0 // indirect
github.com/gogo/protobuf v1.1.1 // indirect

2
go.sum
View File

@ -32,6 +32,8 @@ github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7Vpz
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=

View File

@ -90,7 +90,6 @@ func (c *Config) ActiveView() string {
// SetActiveView set the currently cluster active view
func (c *Config) SetActiveView(view string) {
c.Dump("ActiveView")
c.K9s.ActiveCluster().View.Active = view
}

View File

@ -12,6 +12,9 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// KubeConfig represents kubeconfig settings.
var KubeConfig *Config
// Config tracks a kubernetes configuration.
type Config struct {
flags *genericclioptions.ConfigFlags
@ -23,7 +26,13 @@ type Config struct {
// NewConfig returns a new k8s config or an error if the flags are invalid.
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
return &Config{flags: f}
KubeConfig = &Config{flags: f}
return KubeConfig
}
// Flags returns configuration flags.
func (c *Config) Flags() *genericclioptions.ConfigFlags {
return c.flags
}
// SwitchContext changes the kubeconfig context to a new cluster.

View File

@ -1,9 +1,14 @@
package k8s
import (
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
)
const maxJobNameSize = 42
// CronJob represents a Kubernetes CronJob
type CronJob struct{}
@ -40,3 +45,29 @@ func (c *CronJob) Delete(ns, n string) error {
opts := metav1.DeleteOptions{}
return conn.dialOrDie().BatchV1beta1().CronJobs(ns).Delete(n, &opts)
}
// Run the job associated with this cronjob.
func (c *CronJob) Run(ns, n string) error {
i, err := c.Get(ns, n)
if err != nil {
return err
}
cronJob := i.(*batchv1beta1.CronJob)
var jobName = cronJob.Name
if len(cronJob.Name) >= maxJobNameSize {
jobName = cronJob.Name[0:41]
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: jobName + "-manual-" + rand.String(3),
Namespace: ns,
Labels: cronJob.Spec.JobTemplate.Labels,
},
Spec: cronJob.Spec.JobTemplate.Spec,
}
_, err = conn.dialOrDie().BatchV1().Jobs(ns).Create(job)
return err
}

View File

@ -1,7 +1,13 @@
package k8s
import (
"fmt"
"strings"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest"
)
// Job represents a Kubernetes Job
@ -13,13 +19,13 @@ func NewJob() Res {
}
// Get a Job.
func (c *Job) Get(ns, n string) (interface{}, error) {
func (*Job) Get(ns, n string) (interface{}, error) {
opts := metav1.GetOptions{}
return conn.dialOrDie().BatchV1().Jobs(ns).Get(n, opts)
}
// List all Jobs in a given namespace
func (c *Job) List(ns string) (Collection, error) {
func (*Job) List(ns string) (Collection, error) {
opts := metav1.ListOptions{}
rr, err := conn.dialOrDie().BatchV1().Jobs(ns).List(opts)
@ -36,7 +42,50 @@ func (c *Job) List(ns string) (Collection, error) {
}
// Delete a Job
func (c *Job) Delete(ns, n string) error {
func (*Job) Delete(ns, n string) error {
opts := metav1.DeleteOptions{}
return conn.dialOrDie().BatchV1().Jobs(ns).Delete(n, &opts)
}
// Containers returns all container names on pod
func (j *Job) Containers(ns, n string) ([]string, error) {
pod, err := j.assocPod(ns, n)
if err != nil {
return nil, err
}
log.Debug("Containers found assoc pod", pod)
return NewPod().(Loggable).Containers(ns, pod)
}
// Logs fetch container logs for a given pod and container.
func (j *Job) Logs(ns, n, co string, lines int64, prev bool) *restclient.Request {
pod, err := j.assocPod(ns, n)
if err != nil {
return nil
}
log.Println("Logs found assoc pod", pod)
return NewPod().(Loggable).Logs(ns, pod, co, lines, prev)
}
// Events retrieved jobs events.
func (*Job) Events(ns, n string) (*v1.EventList, error) {
e := conn.dialOrDie().Core().Events(ns)
sel := e.GetFieldSelector(&n, &ns, nil, nil)
opts := metav1.ListOptions{FieldSelector: sel.String()}
ee, err := e.List(opts)
return ee, err
}
func (j *Job) assocPod(ns, n string) (string, error) {
ee, err := j.Events(ns, n)
if err != nil {
return "", err
}
for _, e := range ee.Items {
if strings.Contains(e.Message, "Created pod: ") {
return strings.TrimSpace(strings.Replace(e.Message, "Created pod: ", "", 1)), nil
}
}
return "", fmt.Errorf("unable to find associated pod name for job: %s/%s", ns, n)
}

182
internal/k8s/mapper.go Normal file
View File

@ -0,0 +1,182 @@
package k8s
import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// RestMapping holds k8s resource mapping
// BOZO!! Has to be a better way...
var RestMapping = &RestMapper{}
// RestMapper map resource to REST mapping ie kind, group, version.
type RestMapper struct{}
// Find a mapping given a resource name.
func (*RestMapper) Find(res string) (*meta.RESTMapping, error) {
if m, ok := resMap[res]; ok {
return m, nil
}
return nil, fmt.Errorf("no mapping for resource %s", res)
}
// Name protocol returns rest scope name.
func (*RestMapper) Name() meta.RESTScopeName {
return meta.RESTScopeNameNamespace
}
var resMap = map[string]*meta.RESTMapping{
"ConfigMaps": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmap"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"},
Scope: RestMapping,
},
"Pods": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"},
Scope: RestMapping,
},
"Services": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "service"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
Scope: RestMapping,
},
"EndPoints": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "endpoints"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Endpoints"},
Scope: RestMapping,
},
"Namespaces": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespace"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
Scope: RestMapping,
},
"Nodes": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "node"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"},
Scope: RestMapping,
},
"PersistentVolumes": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolume"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PersistentVolume"},
Scope: RestMapping,
},
"PersistentVolumeClaims": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaim"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PersistentVolumeClaim"},
Scope: RestMapping,
},
"ReplicationControllers": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "replicationcontroller"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ReplicationController"},
Scope: RestMapping,
},
"Secrets": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secret"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"},
Scope: RestMapping,
},
"ServiceAccounts": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "serviceaccount"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ServiceAccount"},
Scope: RestMapping,
},
"Deployments": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Scope: RestMapping,
},
"ReplicaSets": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicaset"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "ReplicaSet"},
Scope: RestMapping,
},
"StatefulSets": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"},
Scope: RestMapping,
},
"HorizontalPodAutoscalers": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscaler"},
GroupVersionKind: schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"},
Scope: RestMapping,
},
"Jobs": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "job"},
GroupVersionKind: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
Scope: RestMapping,
},
"CronJobs": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "cronjob"},
GroupVersionKind: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "CronJob"},
Scope: RestMapping,
},
"DaemonSets": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "daemonset"},
GroupVersionKind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"},
Scope: RestMapping,
},
"Ingress": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingress"},
GroupVersionKind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Ingress"},
Scope: RestMapping,
},
"ClusterRoles": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrole"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"},
Scope: RestMapping,
},
"ClusterRoleBindings": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrolebinding"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"},
Scope: RestMapping,
},
"Roles": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "role"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role"},
Scope: RestMapping,
},
"RoleBindings": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebinding"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"},
Scope: RestMapping,
},
"CustomResourceDefinitions": &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"},
GroupVersionKind: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition"},
Scope: RestMapping,
},
}
// {Group: "certificates.k8s.io", Version: "v1beta1", Kind: "CertificateSigningRequest"}: {},
// {Group: "certificates.k8s.io", Version: "v1beta1", Kind: "CertificateSigningRequestList"}: {},
// {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "MasterConfiguration"}: {},
// {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicy"}: {},
// {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicyList"}: {},
// {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicy"}: {},
// {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicyList"}: {},
// {Group: "policy", Version: "v1beta1", Kind: "PodSecurityPolicy"}: {},
// {Group: "policy", Version: "v1beta1", Kind: "PodSecurityPolicyList"}: {},
// {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPreset"}: {},
// {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPresetList"}: {},
// {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingWebhookConfiguration"}: {},
// {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingWebhookConfigurationList"}: {},
// {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "MutatingWebhookConfiguration"}: {},
// {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "MutatingWebhookConfigurationList"}: {},
// {Group: "auditregistration.k8s.io", Version: "v1alpha1", Kind: "AuditSink"}: {},
// {Group: "auditregistration.k8s.io", Version: "v1alpha1", Kind: "AuditSinkList"}: {},
// {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {},
// {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {},
// {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {},
// {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClassList"}: {},
// {Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"}: {},
// {Group: "storage.k8s.io", Version: "v1", Kind: "StorageClassList"}: {},
// {Group: "authentication.k8s.io", Version: "v1", Kind: "TokenRequest"}: {},

View File

@ -1,7 +1,7 @@
package k8s
import (
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest"
)
@ -9,11 +9,11 @@ import (
const defaultKillGrace int64 = 5
type (
// PodRes represents a K8s pod resource.
PodRes interface {
// Loggable represents a K8s resource that has containers and can be logged.
Loggable interface {
Res
Containers(ns, n string) ([]string, error)
Logs(ns, n, co string, lines int64) *restclient.Request
Logs(ns, n, co string, lines int64, previous bool) *restclient.Request
}
// Pod represents a Kubernetes resource.
@ -73,12 +73,14 @@ func (*Pod) Containers(ns, n string) ([]string, error) {
}
// Logs fetch container logs for a given pod and container.
func (*Pod) Logs(ns, n, co string, lines int64) *restclient.Request {
func (*Pod) Logs(ns, n, co string, lines int64, prev bool) *restclient.Request {
opts := &v1.PodLogOptions{
Container: co,
Follow: true,
TailLines: &lines,
Previous: prev,
}
return conn.dialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
}

View File

@ -9,6 +9,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/describe"
versioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
)
type (
@ -66,6 +68,24 @@ func (b *Base) List(ns string) (Columnars, error) {
return cc, nil
}
// Describe a given resource.
func (b *Base) Describe(kind, pa string) (string, error) {
ns, n := namespaced(pa)
mapping, err := k8s.RestMapping.Find(kind)
if err != nil {
return "", err
}
d, err := versioned.Describer(k8s.KubeConfig.Flags(), mapping)
if err != nil {
return "", err
}
opts := describe.DescriberSettings{
ShowEvents: true,
}
return d.Describe(ns, n, opts)
}
// Delete a resource by name.
func (b *Base) Delete(path string) error {
ns, n := namespaced(path)

View File

@ -21,7 +21,7 @@ func NewConfigMapList(ns string) List {
// NewConfigMapListWithArgs returns a new resource list.
func NewConfigMapListWithArgs(ns string, res Resource) List {
return newList(ns, "cm", res, AllVerbsAccess)
return newList(ns, "cm", res, AllVerbsAccess|DescribeAccess)
}
// NewConfigMap instantiates a new ConfigMap.

View File

@ -19,7 +19,7 @@ func NewClusterRoleList(ns string) List {
// NewClusterRoleListWithArgs returns a new resource list.
func NewClusterRoleListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "clusterrole", res, CRUDAccess)
return newList(NotNamespaced, "clusterrole", res, CRUDAccess|DescribeAccess)
}
// NewClusterRole instantiates a new ClusterRole.

View File

@ -19,7 +19,7 @@ func NewClusterRoleBindingList(ns string) List {
// NewClusterRoleBindingListWithArgs returns a new resource list.
func NewClusterRoleBindingListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "ctx", res, SwitchAccess|ViewAccess|DeleteAccess)
return newList(NotNamespaced, "ctx", res, SwitchAccess|ViewAccess|DeleteAccess|DescribeAccess)
}
// NewClusterRoleBinding instantiates a new ClusterRoleBinding.

View File

@ -23,7 +23,7 @@ func NewCRDList(ns string) List {
// NewCRDListWithArgs returns a new resource list.
func NewCRDListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "crd", res, CRUDAccess)
return newList(NotNamespaced, "crd", res, CRUDAccess|DescribeAccess)
}
// NewCRD instantiates a new CRD.

View File

@ -1,6 +1,7 @@
package resource
import (
"fmt"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -8,11 +9,23 @@ import (
batchv1beta1 "k8s.io/api/batch/v1beta1"
)
// CronJob tracks a kubernetes resource.
type CronJob struct {
*Base
instance *batchv1beta1.CronJob
}
type (
// CronJob tracks a kubernetes resource.
CronJob struct {
*Base
instance *batchv1beta1.CronJob
}
// Runner can run jobs.
Runner interface {
Run(path string) error
}
// Runnable can run jobs.
Runnable interface {
Run(ns, n string) error
}
)
// NewCronJobList returns a new resource list.
func NewCronJobList(ns string) List {
@ -21,7 +34,7 @@ func NewCronJobList(ns string) List {
// NewCronJobListWithArgs returns a new resource list.
func NewCronJobListWithArgs(ns string, res Resource) List {
return newList(ns, "job", res, AllVerbsAccess)
return newList(ns, "cronjob", res, AllVerbsAccess|DescribeAccess)
}
// NewCronJob instantiates a new CronJob.
@ -70,6 +83,15 @@ func (r *CronJob) Marshal(path string) (string, error) {
return r.marshalObject(cj)
}
// Run a given cronjob.
func (r *CronJob) Run(pa string) error {
ns, n := namespaced(pa)
if c, ok := r.caller.(Runnable); ok {
return c.Run(ns, n)
}
return fmt.Errorf("unable to run cronjob %s", pa)
}
// Header return resource header.
func (*CronJob) Header(ns string) Row {
hh := Row{}
@ -107,5 +129,3 @@ func (r *CronJob) Fields(ns string) Row {
func (*CronJob) ExtFields() Properties {
return Properties{}
}
// Helpers...

View File

@ -17,7 +17,7 @@ func TestCronJobListAccess(t *testing.T) {
l.SetNamespace(ns)
assert.Equal(t, "blee", l.GetNamespace())
assert.Equal(t, "job", l.GetName())
assert.Equal(t, "cronjob", l.GetName())
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
assert.True(t, l.Access(a))
}

View File

@ -21,7 +21,7 @@ func NewDeploymentList(ns string) List {
// NewDeploymentListWithArgs returns a new resource list.
func NewDeploymentListWithArgs(ns string, res Resource) List {
return newList(ns, "deploy", res, AllVerbsAccess)
return newList(ns, "deploy", res, AllVerbsAccess|DescribeAccess)
}
// NewDeployment instantiates a new Deployment.

View File

@ -21,7 +21,7 @@ func NewDaemonSetList(ns string) List {
// NewDaemonSetListWithArgs returns a new resource list.
func NewDaemonSetListWithArgs(ns string, res Resource) List {
return newList(ns, "ds", res, AllVerbsAccess)
return newList(ns, "ds", res, AllVerbsAccess|DescribeAccess)
}
// NewDaemonSet instantiates a new DaemonSet.

View File

@ -23,7 +23,7 @@ func NewEndpointsList(ns string) List {
// NewEndpointsListWithArgs returns a new resource list.
func NewEndpointsListWithArgs(ns string, res Resource) List {
return newList(ns, "ep", res, AllVerbsAccess)
return newList(ns, "ep", res, AllVerbsAccess|DescribeAccess)
}
// NewEndpoints instantiates a new Endpoint.

View File

@ -22,7 +22,7 @@ func NewHPAList(ns string) List {
// NewHPAListWithArgs returns a new resource list.
func NewHPAListWithArgs(ns string, res Resource) List {
return newList(ns, "hpa", res, AllVerbsAccess)
return newList(ns, "hpa", res, AllVerbsAccess|DescribeAccess)
}
// NewHPA instantiates a new Endpoint.

View File

@ -22,7 +22,7 @@ func NewIngressList(ns string) List {
// NewIngressListWithArgs returns a new resource list.
func NewIngressListWithArgs(ns string, res Resource) List {
return newList(ns, "ing", res, AllVerbsAccess)
return newList(ns, "ing", res, AllVerbsAccess|DescribeAccess)
}
// NewIngress instantiates a new Endpoint.

View File

@ -1,6 +1,8 @@
package resource
import (
"bufio"
"context"
"fmt"
"time"
@ -23,7 +25,7 @@ func NewJobList(ns string) List {
// NewJobListWithArgs returns a new resource list.
func NewJobListWithArgs(ns string, res Resource) List {
return newList(ns, "job", res, AllVerbsAccess)
return newList(ns, "job", res, AllVerbsAccess|DescribeAccess)
}
// NewJob instantiates a new Job.
@ -72,6 +74,49 @@ func (r *Job) Marshal(path string) (string, error) {
return r.marshalObject(jo)
}
func (r *Job) Containers(path string) ([]string, error) {
ns, n := namespaced(path)
return r.caller.(k8s.Loggable).Containers(ns, n)
}
func (r *Job) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) {
req := r.caller.(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 {
return cancel, fmt.Errorf("Log tail request failed for job `%s/%s:%s", ns, n, co)
}
go func() {
defer func() {
stream.Close()
cancel()
close(c)
}()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
c <- scanner.Text()
}
}()
return cancel, nil
}
// Header return resource header.
func (*Job) Header(ns string) Row {
hh := Row{}

View File

@ -102,6 +102,7 @@ type (
Get(path string) (Columnar, error)
List(ns string) (Columnars, error)
Delete(path string) error
Describe(kind, pa string) (string, error)
Marshal(pa string) (string, error)
Header(ns string) Row
}

View File

@ -30,7 +30,7 @@ func NewNodeList(ns string) List {
// NewNodeListWithArgs returns a new resource list.
func NewNodeListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "no", res, ViewAccess)
return newList(NotNamespaced, "no", res, ViewAccess|DescribeAccess)
}
// NewNode instantiates a new Endpoint.

View File

@ -19,7 +19,7 @@ func NewNamespaceList(ns string) List {
// NewNamespaceListWithArgs returns a new resource list.
func NewNamespaceListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "ns", res, CRUDAccess)
return newList(NotNamespaced, "ns", res, CRUDAccess|DescribeAccess)
}
// NewNamespace instantiates a new Endpoint.

View File

@ -22,7 +22,7 @@ type (
// Tailable represents a resource with tailable logs.
Tailable interface {
Logs(c chan<- string, ns, na, co string, lines int64) (context.CancelFunc, error)
Logs(c chan<- string, ns, na, co string, lines int64, prev bool) (context.CancelFunc, error)
}
// TailableResource is a resource that have tailable logs.
@ -47,7 +47,7 @@ func NewPodList(ns string) List {
// NewPodListWithArgs returns a new resource list.
func NewPodListWithArgs(ns string, res Resource) List {
l := newList(ns, "po", res, AllVerbsAccess)
l := newList(ns, "po", res, AllVerbsAccess|DescribeAccess)
l.xray = true
return l
}
@ -116,12 +116,21 @@ func (r *Pod) Marshal(path string) (string, error) {
// Containers lists out all the docker contrainers name contained in a pod.
func (r *Pod) Containers(path string) ([]string, error) {
ns, po := namespaced(path)
return r.caller.(k8s.PodRes).Containers(ns, po)
return r.caller.(k8s.Loggable).Containers(ns, po)
}
// Logs tails a given container logs
func (r *Pod) Logs(c chan<- string, ns, n, co string, lines int64) (context.CancelFunc, error) {
req := r.caller.(k8s.PodRes).Logs(ns, n, co, lines)
func (r *Pod) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) {
// var ctn v1.Container
// for _, c := range r.instance.Spec.Containers {
// if c.Name == co {
// ctn = c
// }
// }
// if ctn.Status.ContainerStatus
req := r.caller.(k8s.Loggable).Logs(ns, n, co, lines, prev)
ctx, cancel := context.WithCancel(context.TODO())
req.Context(ctx)

View File

@ -22,7 +22,7 @@ func NewPVList(ns string) List {
// NewPVListWithArgs returns a new resource list.
func NewPVListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "pv", res, CRUDAccess)
return newList(NotNamespaced, "pv", res, CRUDAccess|DescribeAccess)
}
// NewPV instantiates a new Endpoint.

View File

@ -19,7 +19,7 @@ func NewPVCList(ns string) List {
// NewPVCListWithArgs returns a new resource list.
func NewPVCListWithArgs(ns string, res Resource) List {
return newList(ns, "pvc", res, AllVerbsAccess)
return newList(ns, "pvc", res, AllVerbsAccess|DescribeAccess)
}
// NewPVC instantiates a new Endpoint.

View File

@ -21,7 +21,7 @@ func NewReplicationControllerList(ns string) List {
// NewReplicationControllerListWithArgs returns a new resource list.
func NewReplicationControllerListWithArgs(ns string, res Resource) List {
return newList(ns, "rc", res, AllVerbsAccess)
return newList(ns, "rc", res, AllVerbsAccess|DescribeAccess)
}
// NewReplicationController instantiates a new Endpoint.

View File

@ -21,7 +21,7 @@ func NewRoleBindingList(ns string) List {
// NewRoleBindingListWithArgs returns a new resource list.
func NewRoleBindingListWithArgs(ns string, res Resource) List {
return newList(ns, "rolebinding", res, AllVerbsAccess)
return newList(ns, "rolebinding", res, AllVerbsAccess|DescribeAccess)
}
// NewRoleBinding instantiates a new Endpoint.

View File

@ -21,7 +21,7 @@ func NewReplicaSetList(ns string) List {
// NewReplicaSetListWithArgs returns a new resource list.
func NewReplicaSetListWithArgs(ns string, res Resource) List {
return newList(ns, "rs", res, AllVerbsAccess)
return newList(ns, "rs", res, AllVerbsAccess|DescribeAccess)
}
// NewReplicaSet instantiates a new Endpoint.

View File

@ -21,7 +21,7 @@ func NewServiceAccountList(ns string) List {
// NewServiceAccountListWithArgs returns a new resource list.
func NewServiceAccountListWithArgs(ns string, res Resource) List {
return newList(NotNamespaced, "sa", res, CRUDAccess)
return newList(ns, "sa", res, AllVerbsAccess|DescribeAccess)
}
// NewServiceAccount instantiates a new Endpoint.

View File

@ -16,7 +16,7 @@ func TestSaListAccess(t *testing.T) {
l := resource.NewServiceAccountList(resource.AllNamespaces)
l.SetNamespace(ns)
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
assert.Equal(t, ns, l.GetNamespace())
assert.Equal(t, "sa", l.GetName())
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
assert.True(t, l.Access(a))

View File

@ -21,7 +21,7 @@ func NewSecretList(ns string) List {
// NewSecretListWithArgs returns a new resource list.
func NewSecretListWithArgs(ns string, res Resource) List {
return newList(ns, "secret", res, AllVerbsAccess)
return newList(ns, "secret", res, AllVerbsAccess|DescribeAccess)
}
// NewSecret instantiates a new Secret.

View File

@ -21,7 +21,7 @@ func NewStatefulSetList(ns string) List {
// NewStatefulSetListWithArgs returns a new resource list.
func NewStatefulSetListWithArgs(ns string, res Resource) List {
return newList(ns, "sts", res, AllVerbsAccess)
return newList(ns, "sts", res, AllVerbsAccess|DescribeAccess)
}
// NewStatefulSet instantiates a new Endpoint.

View File

@ -25,7 +25,7 @@ func NewServiceList(ns string) List {
// NewServiceListWithArgs returns a new resource list.
func NewServiceListWithArgs(ns string, res Resource) List {
return newList(ns, "svc", res, AllVerbsAccess)
return newList(ns, "svc", res, AllVerbsAccess|DescribeAccess)
}
// NewService instantiates a new Endpoint.

View File

@ -1,9 +1,5 @@
package views
import (
log "github.com/sirupsen/logrus"
)
const maxBuff = 10
type buffWatcher interface {
@ -27,12 +23,10 @@ func newCmdBuff(key rune) *cmdBuff {
}
func (c *cmdBuff) isActive() bool {
log.Debugf("Cmd buff `%s Active:%t", string(c.hotKey), c.active)
return c.active
}
func (c *cmdBuff) setActive(b bool) {
log.Debugf("Cmd buff `%s SetActive:%t", string(c.hotKey), b)
c.active = b
c.fireActive(c.active)
}

View File

@ -5,7 +5,6 @@ import (
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/watch"
)
@ -22,7 +21,7 @@ const (
func defaultColorer(ns string, r *resource.RowEvent) tcell.Color {
c := stdColor
switch r.Action {
case watch.Added:
case watch.Added, resource.New:
c = addColor
case watch.Modified:
c = modColor
@ -37,7 +36,6 @@ func podColorer(ns string, r *resource.RowEvent) tcell.Color {
if len(ns) != 0 {
statusCol = 2
}
log.Debug("Status", strings.TrimSpace(r.Fields[statusCol]))
switch strings.TrimSpace(r.Fields[statusCol]) {
case "ContainerCreating":
return addColor

36
internal/views/cronjob.go Normal file
View File

@ -0,0 +1,36 @@
package views
import (
"fmt"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
)
type cronJobView struct {
*resourceView
}
func newCronJobView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
v := cronJobView{
resourceView: newResourceView(t, app, list, c).(*resourceView),
}
v.extraActionsFn = v.extraActions
v.switchPage("cronjob")
return &v
}
func (v *cronJobView) trigger(*tcell.EventKey) {
if !v.rowSelected() {
return
}
v.app.flash(flashInfo, fmt.Sprintf("Triggering %s %s", v.list.GetName(), v.selectedItem))
if err := v.list.Resource().(resource.Runner).Run(v.selectedItem); err != nil {
v.app.flash(flashErr, "Boom!", err.Error())
}
}
func (v *cronJobView) extraActions(aa keyActions) {
aa[tcell.KeyCtrlT] = keyAction{description: "Trigger", action: v.trigger}
}

View File

@ -7,13 +7,14 @@ import (
"github.com/k8sland/tview"
)
const detailFmt = " [aqua::-]%s [fuchsia::b]YAML "
const detailFmt = " [aqua::-]%s [fuchsia::b]%s "
// detailsView display yaml output
type detailsView struct {
*tview.TextView
actions keyActions
actions keyActions
category string
}
func newDetailsView() *detailsView {
@ -25,6 +26,10 @@ func newDetailsView() *detailsView {
return &v
}
func (v *detailsView) setCategory(n string) {
v.category = n
}
func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if evt.Key() == tcell.KeyRune {
if a, ok := v.actions[evt.Key()]; ok {
@ -54,5 +59,5 @@ func (v *detailsView) hints() hints {
}
func (v *detailsView) setTitle(t string) {
v.SetTitle(fmt.Sprintf(detailFmt, t))
v.SetTitle(fmt.Sprintf(detailFmt, t, v.category))
}

57
internal/views/job.go Normal file
View File

@ -0,0 +1,57 @@
package views
import (
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
log "github.com/sirupsen/logrus"
)
type jobView struct {
*resourceView
}
func newJobView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
v := jobView{newResourceView(t, app, list, c).(*resourceView)}
v.extraActionsFn = v.extraActions
v.AddPage("logs", newLogsView(&v), true, false)
v.switchPage("job")
return &v
}
// Protocol...
func (v *jobView) appView() *appView {
return v.app
}
func (v *jobView) getList() resource.List {
return v.list
}
func (v *jobView) getSelection() string {
return v.selectedItem
}
// Handlers...
func (v *jobView) logs(*tcell.EventKey) {
if !v.rowSelected() {
return
}
cc, err := fetchContainers(v.list, v.selectedItem)
if err != nil {
log.Error(err)
}
l := v.GetPrimitive("logs").(*logsView)
l.deleteAllPages()
for _, c := range cc {
l.addContainer(c)
}
v.switchPage("logs")
l.init()
}
func (v *jobView) extraActions(aa keyActions) {
aa[KeyL] = newKeyHandler("Logs", v.logs)
}

View File

@ -14,17 +14,17 @@ type logView struct {
*tview.TextView
}
func newLogView(title string, pv *podView) *logView {
func newLogView(title string, parent loggable) *logView {
v := logView{TextView: tview.NewTextView()}
{
v.SetScrollable(true)
v.SetDynamicColors(true)
v.SetBorder(true)
v.SetBorderPadding(0, 0, 1, 1)
v.SetTitle(fmt.Sprintf(logTitleFmt, pv.selectedItem, title))
v.SetTitle(fmt.Sprintf(logTitleFmt, parent.getSelection(), title))
v.SetWrap(false)
v.SetChangedFunc(func() {
pv.app.Draw()
parent.appView().Draw()
})
}
return &v

View File

@ -22,18 +22,18 @@ const (
type logsView struct {
*tview.Pages
pv *podView
parent loggable
containers []string
actions keyActions
cancelFunc context.CancelFunc
buffer *logBuffer
}
func newLogsView(pv *podView) *logsView {
func newLogsView(parent loggable) *logsView {
maxBuff := config.Root.K9s.LogBufferSize
v := logsView{
Pages: tview.NewPages(),
pv: pv,
parent: parent,
containers: []string{},
buffer: newLogBuffer(int(maxBuff), true),
}
@ -42,8 +42,8 @@ func newLogsView(pv *podView) *logsView {
KeyC: {description: "Clear", action: v.clearLogs},
KeyU: {description: "Top", action: v.top},
KeyD: {description: "Bottom", action: v.bottom},
KeyF: {description: "Page Up", action: v.pageUp},
KeyB: {description: "Page Down", action: v.pageDown},
KeyF: {description: "PageUp", action: v.pageUp},
KeyB: {description: "PageDown", action: v.pageDown},
})
v.SetInputCapture(v.keyboard)
@ -98,7 +98,7 @@ func (v *logsView) hints() hints {
func (v *logsView) addContainer(n string) {
v.containers = append(v.containers, n)
l := newLogView(n, v.pv)
l := newLogView(n, v.parent)
l.SetInputCapture(v.keyboard)
v.AddPage(n, l, true, false)
}
@ -121,14 +121,14 @@ func (v *logsView) load(i int) {
}
v.SwitchToPage(v.containers[i])
v.buffer.clear()
if err := v.doLoad(v.pv.selectedItem, v.containers[i]); err != nil {
v.pv.app.flash(flashErr, err.Error())
if err := v.doLoad(v.parent.getSelection(), v.containers[i]); err != nil {
v.parent.appView().flash(flashErr, err.Error())
v.buffer.add("😂 Doh! No logs are available at this time. Check again later on...")
l := v.CurrentPage().Item.(*logView)
l.log(v.buffer)
return
}
v.pv.app.SetFocus(v)
v.parent.appView().SetFocus(v)
}
func (v *logsView) killLogIfAny() {
@ -149,6 +149,11 @@ func (v *logsView) doLoad(path, co string) error {
select {
case line, ok := <-c:
if !ok {
if v.buffer.length() > 0 {
v.buffer.add("--- No more logs ---")
l.log(v.buffer)
l.ScrollToEnd()
}
return
}
v.buffer.add(line)
@ -173,12 +178,12 @@ func (v *logsView) doLoad(path, co string) error {
}()
ns, po := namespaced(path)
res, ok := v.pv.list.Resource().(resource.Tailable)
res, ok := v.parent.getList().Resource().(resource.Tailable)
if !ok {
return fmt.Errorf("Resource %T is not tailable", v.pv.list.Resource)
return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource)
}
maxBuff := config.Root.K9s.LogBufferSize
cancelFn, err := res.Logs(c, ns, po, co, int64(maxBuff))
cancelFn, err := res.Logs(c, ns, po, co, int64(maxBuff), false)
if err != nil {
cancelFn()
return err
@ -193,40 +198,40 @@ func (v *logsView) doLoad(path, co string) error {
func (v *logsView) back(*tcell.EventKey) {
v.stop()
v.pv.switchPage(v.pv.list.GetName())
v.parent.switchPage(v.parent.getList().GetName())
}
func (v *logsView) top(*tcell.EventKey) {
if p := v.CurrentPage(); p != nil {
v.pv.app.flash(flashInfo, "Top logs...")
v.parent.appView().flash(flashInfo, "Top logs...")
p.Item.(*logView).ScrollToBeginning()
}
}
func (v *logsView) bottom(*tcell.EventKey) {
if p := v.CurrentPage(); p != nil {
v.pv.app.flash(flashInfo, "Bottom logs...")
v.parent.appView().flash(flashInfo, "Bottom logs...")
p.Item.(*logView).ScrollToEnd()
}
}
func (v *logsView) pageUp(*tcell.EventKey) {
if p := v.CurrentPage(); p != nil {
v.pv.app.flash(flashInfo, "Page Up logs...")
v.parent.appView().flash(flashInfo, "Page Up logs...")
p.Item.(*logView).PageUp()
}
}
func (v *logsView) pageDown(*tcell.EventKey) {
if p := v.CurrentPage(); p != nil {
v.pv.app.flash(flashInfo, "Page Down logs...")
v.parent.appView().flash(flashInfo, "Page Down logs...")
p.Item.(*logView).PageDown()
}
}
func (v *logsView) clearLogs(*tcell.EventKey) {
if p := v.CurrentPage(); p != nil {
v.pv.app.flash(flashInfo, "Clearing logs...")
v.parent.appView().flash(flashInfo, "Clearing logs...")
v.buffer.clear()
p.Item.(*logView).Clear()
}

View File

@ -12,10 +12,9 @@ import (
)
const (
menuFmt = " [dodgerblue::b]%s[white::d]%s "
menuSepFmt = " [dodgerblue::b]<%s> [white::d]%s "
menuSepFmt = " [dodgerblue::b]%-8s [white::d]%s "
menuIndexFmt = " [fuchsia::b]<%d> [white::d]%s "
maxRows = 5
maxRows = 6
colLen = 20
)
@ -98,19 +97,17 @@ func (v *menuView) setMenu(hh hints) {
}
}
func (*menuView) toMnemonic(s string) string {
return "<" + strings.ToLower(s) + ">"
}
func (v *menuView) item(h hint) string {
i, err := strconv.Atoi(h.mnemonic)
if err == nil {
return fmt.Sprintf(menuIndexFmt, i, resource.Truncate(h.display, 14))
}
var s string
if len(h.mnemonic) == 1 {
s = fmt.Sprintf(menuSepFmt, strings.ToLower(h.mnemonic), h.display)
} else {
s = fmt.Sprintf(menuSepFmt, strings.ToUpper(h.mnemonic), h.display)
}
return s
return fmt.Sprintf(menuSepFmt, v.toMnemonic(h.mnemonic), h.display)
}
func (a keyActions) toHints() hints {

View File

@ -10,6 +10,13 @@ type podView struct {
*resourceView
}
type loggable interface {
appView() *appView
getSelection() string
getList() resource.List
switchPage(n string)
}
func newPodView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
v := podView{newResourceView(t, app, list, c).(*resourceView)}
v.extraActionsFn = v.extraActions
@ -31,6 +38,18 @@ func newPodView(t string, app *appView, list resource.List, c colorerFn) resourc
return &v
}
// Protocol...
func (v *podView) appView() *appView {
return v.app
}
func (v *podView) getList() resource.List {
return v.list
}
func (v *podView) getSelection() string {
return v.selectedItem
}
// Handlers...
func (v *podView) logs(*tcell.EventKey) {

View File

@ -22,37 +22,37 @@ type (
var cmdMap = map[string]resCmd{
"cm": {
title: "Config Maps",
title: "ConfigMaps",
api: "core",
viewFn: newResourceView,
listFn: resource.NewConfigMapList,
colorerFn: defaultColorer,
},
"cr": {
title: "Cluster Roles",
title: "ClusterRoles",
api: "rbac.authorization.k8s.io",
viewFn: newResourceView,
listFn: resource.NewClusterRoleList,
colorerFn: defaultColorer,
},
"crb": {
title: "Cluster Role Bindings",
title: "ClusterRoleBindings",
api: "rbac.authorization.k8s.io",
viewFn: newResourceView,
listFn: resource.NewClusterRoleBindingList,
colorerFn: defaultColorer,
},
"crd": {
title: "Custom Resource Definitions",
title: "CustomResourceDefinitions",
api: "apiextensions.k8s.io",
viewFn: newResourceView,
listFn: resource.NewCRDList,
colorerFn: defaultColorer,
},
"cjo": {
"cron": {
title: "CronJobs",
api: "batch",
viewFn: newResourceView,
viewFn: newCronJobView,
listFn: resource.NewCronJobList,
colorerFn: defaultColorer,
},
@ -92,7 +92,7 @@ var cmdMap = map[string]resCmd{
colorerFn: evColorer,
},
"hpa": {
title: "Horizontal Pod Autoscalers",
title: "HorizontalPodAutoscalers",
api: "autoscaling",
viewFn: newResourceView,
listFn: resource.NewHPAList,
@ -105,10 +105,10 @@ var cmdMap = map[string]resCmd{
listFn: resource.NewIngressList,
colorerFn: defaultColorer,
},
"jo": {
"job": {
title: "Jobs",
api: "batch",
viewFn: newResourceView,
viewFn: newJobView,
listFn: resource.NewJobList,
colorerFn: defaultColorer,
},
@ -134,28 +134,28 @@ var cmdMap = map[string]resCmd{
colorerFn: podColorer,
},
"pv": {
title: "Persistent Volumes",
title: "PersistentVolumes",
api: "core",
viewFn: newResourceView,
listFn: resource.NewPVList,
colorerFn: pvColorer,
},
"pvc": {
title: "Persistent Volume Claims",
title: "PersistentVolumeClaims",
api: "core",
viewFn: newResourceView,
listFn: resource.NewPVCList,
colorerFn: pvcColorer,
},
"rb": {
title: "Role Bindings",
title: "RoleBindings",
api: "rbac.authorization.k8s.io",
viewFn: newResourceView,
listFn: resource.NewRoleBindingList,
colorerFn: defaultColorer,
},
"rc": {
title: "ReplicationController",
title: "ReplicationControllers",
api: "v1",
viewFn: newResourceView,
listFn: resource.NewReplicationControllerList,
@ -169,14 +169,14 @@ var cmdMap = map[string]resCmd{
colorerFn: defaultColorer,
},
"rs": {
title: "Replica Sets",
title: "ReplicaSets",
api: "apps",
viewFn: newResourceView,
listFn: resource.NewReplicaSetList,
colorerFn: rsColorer,
},
"sa": {
title: "Service Accounts",
title: "ServiceAccounts",
api: "core",
viewFn: newResourceView,
listFn: resource.NewServiceAccountList,

View File

@ -145,22 +145,49 @@ func (v *resourceView) delete(*tcell.EventKey) {
v.selectedItem = noSelection
}
func (v *resourceView) describe(*tcell.EventKey) {
details := v.GetPrimitive("xray").(details)
details.clear()
// func (v *resourceView) xRay(*tcell.EventKey) {
// details := v.GetPrimitive("xray").(details)
// details.clear()
// if !v.rowSelected() {
// return
// }
// props, err := v.list.Describe(v.selectedItem)
// if err != nil {
// v.app.flash(flashErr, "Unable to get xray fields", err.Error())
// return
// }
// details.update(props)
// details.setTitle(fmt.Sprintf(" %s ", v.selectedItem))
// v.switchPage("xray")
// }
func (v *resourceView) describe(*tcell.EventKey) {
if !v.rowSelected() {
return
}
props, err := v.list.Describe(v.selectedItem)
selected := v.selectedItem
selected = strings.Replace(selected, "+", "", -1)
selected = strings.Replace(selected, "(*)", "", -1)
raw, err := v.list.Resource().Describe(v.title, selected)
if err != nil {
v.app.flash(flashErr, "Unable to get xray fields", err.Error())
v.app.flash(flashErr, "Unable to describe this resource", err.Error())
log.Error(err)
return
}
details.update(props)
details.setTitle(fmt.Sprintf(" %s ", v.selectedItem))
v.switchPage("xray")
var re = regexp.MustCompile(`(?m:(^(.+)$))`)
str := re.ReplaceAllString(string(raw), `[aqua]$1`)
details := v.GetPrimitive("details").(*detailsView)
details.ScrollToBeginning()
details.setCategory("DESC")
details.SetText(str)
details.setTitle(selected)
v.switchPage("details")
}
func (v *resourceView) view(*tcell.EventKey) {
@ -175,10 +202,12 @@ func (v *resourceView) view(*tcell.EventKey) {
return
}
var re = regexp.MustCompile(`([\w|\.|"|\-|\/|\@]+):(.*)`)
var re = regexp.MustCompile(`(?m:([\w|\.|"|\-|\/|\@]+):(.*)$)`)
str := re.ReplaceAllString(string(raw), `[aqua]$1: [white]$2`)
details := v.GetPrimitive("details").(*detailsView)
details.ScrollToBeginning()
details.setCategory("YAML")
details.SetText(str)
details.setTitle(v.selectedItem)
v.switchPage("details")
@ -341,7 +370,7 @@ func (v *resourceView) refreshActions() {
aa[KeyV] = newKeyHandler("View", v.view)
}
if v.list.Access(resource.DescribeAccess) {
aa[tcell.KeyCtrlX] = newKeyHandler("Describe", v.describe)
aa[KeyD] = newKeyHandler("Describe", v.describe)
}
aa[KeyHelp] = newKeyHandler("Help", v.app.noop)