Added cronjob triggers. Tx dzoeteman! + describe support + job logger + bugs
parent
787f9ff15c
commit
823169ce79
|
|
@ -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!!
|
||||
|
||||
|
|
|
|||
|
|
@ -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
1
go.mod
|
|
@ -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
2
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}: {},
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
|
|
@ -8,12 +9,24 @@ import (
|
|||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
)
|
||||
|
||||
type (
|
||||
// CronJob tracks a kubernetes resource.
|
||||
type CronJob struct {
|
||||
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 {
|
||||
return NewCronJobListWithArgs(ns, NewCronJob())
|
||||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
}
|
||||
|
|
@ -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
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ var cmdMap = map[string]resCmd{
|
|||
listFn: resource.NewCRDList,
|
||||
colorerFn: defaultColorer,
|
||||
},
|
||||
"cjo": {
|
||||
"cron": {
|
||||
title: "CronJobs",
|
||||
api: "batch",
|
||||
viewFn: newResourceView,
|
||||
viewFn: newCronJobView,
|
||||
listFn: resource.NewCronJobList,
|
||||
colorerFn: defaultColorer,
|
||||
},
|
||||
|
|
@ -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,
|
||||
},
|
||||
|
|
@ -155,7 +155,7 @@ var cmdMap = map[string]resCmd{
|
|||
colorerFn: defaultColorer,
|
||||
},
|
||||
"rc": {
|
||||
title: "ReplicationController",
|
||||
title: "ReplicationControllers",
|
||||
api: "v1",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewReplicationControllerList,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue