mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
Adding a Kubernetes module for displaying Kubernetes cluster resources and info
Signed-off-by: Andrew Suderman <andrew@sudermanjr.com>
This commit is contained in:
parent
97ca61ac97
commit
da0e1dbd5b
BIN
_sample_configs/kubernetes_config.png
Normal file
BIN
_sample_configs/kubernetes_config.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 278 KiB |
21
_sample_configs/kubernetes_config.yml
Normal file
21
_sample_configs/kubernetes_config.yml
Normal file
@ -0,0 +1,21 @@
|
||||
wtf:
|
||||
colors:
|
||||
border:
|
||||
focusable: darkslateblue
|
||||
focused: orange
|
||||
normal: gray
|
||||
grid:
|
||||
columns: [32, 32, 32, 32, 32, 32]
|
||||
rows: [10, 10, 10, 10, 10, 10]
|
||||
refreshInterval: 2
|
||||
mods:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
kubeconfig: /Users/testuser/.kube/config
|
||||
namespaces: ["demo", "kube-system"]
|
||||
objects: ["nodes","deployments", "pods"]
|
||||
position:
|
||||
top: 0
|
||||
left: 0
|
||||
height: 6
|
||||
width: 3
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/wtfutil/wtf/modules/ipaddresses/ipinfo"
|
||||
"github.com/wtfutil/wtf/modules/jenkins"
|
||||
"github.com/wtfutil/wtf/modules/jira"
|
||||
"github.com/wtfutil/wtf/modules/kubernetes"
|
||||
"github.com/wtfutil/wtf/modules/logger"
|
||||
"github.com/wtfutil/wtf/modules/mercurial"
|
||||
"github.com/wtfutil/wtf/modules/nbascore"
|
||||
@ -144,6 +145,9 @@ func MakeWidget(
|
||||
case "jira":
|
||||
settings := jira.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = jira.NewWidget(app, pages, settings)
|
||||
case "kubernetes":
|
||||
settings := kubernetes.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = kubernetes.NewWidget(app, settings)
|
||||
case "logger":
|
||||
settings := logger.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = logger.NewWidget(app, settings)
|
||||
|
37
modules/kubernetes/client.go
Normal file
37
modules/kubernetes/client.go
Normal file
@ -0,0 +1,37 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
var kubeClient *clientInstance
|
||||
var clientOnce sync.Once
|
||||
|
||||
type clientInstance struct {
|
||||
Client kubernetes.Interface
|
||||
}
|
||||
|
||||
// getInstance returns a Kubernetes interface for a clientset
|
||||
func (widget *Widget) getInstance() *clientInstance {
|
||||
clientOnce.Do(func() {
|
||||
if kubeClient == nil {
|
||||
kubeClient = &clientInstance{
|
||||
Client: widget.getKubeClient(),
|
||||
}
|
||||
}
|
||||
})
|
||||
return kubeClient
|
||||
}
|
||||
|
||||
// getKubeClient returns a kubernetes clientset for the kubeconfig provided
|
||||
func (widget *Widget) getKubeClient() kubernetes.Interface {
|
||||
config, _ := clientcmd.BuildConfigFromFlags("", widget.kubeconfig)
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
panic(err.Error)
|
||||
}
|
||||
return clientset
|
||||
}
|
32
modules/kubernetes/settings.go
Normal file
32
modules/kubernetes/settings.go
Normal file
@ -0,0 +1,32 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
)
|
||||
|
||||
const defaultTitle = "Kubernetes"
|
||||
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
|
||||
objects []string `help:"Kubernetes objects to show. Options are pods, deployments."`
|
||||
title string `help:"Override the title of widget."`
|
||||
kubeconfig string `help:"Location of a kubeconfig file."`
|
||||
namespaces []string `help:"List of namespaces to watch. If blank, defaults to all namespaces."`
|
||||
}
|
||||
|
||||
func NewSettingsFromYAML(name string, moduleConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, moduleConfig, globalConfig),
|
||||
|
||||
objects: utils.ToStrs(moduleConfig.UList("objects")),
|
||||
title: moduleConfig.UString("title"),
|
||||
kubeconfig: moduleConfig.UString("kubeconfig"),
|
||||
namespaces: utils.ToStrs(moduleConfig.UList("namespaces")),
|
||||
}
|
||||
|
||||
return &settings
|
||||
}
|
209
modules/kubernetes/widget.go
Normal file
209
modules/kubernetes/widget.go
Normal file
@ -0,0 +1,209 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Widget contains all the config for the widget
|
||||
type Widget struct {
|
||||
view.TextWidget
|
||||
|
||||
objects []string
|
||||
title string
|
||||
kubeconfig string
|
||||
namespaces []string
|
||||
settings *Settings
|
||||
}
|
||||
|
||||
// NewWidget creates a new instance of the widget
|
||||
func NewWidget(app *tview.Application, settings *Settings) *Widget {
|
||||
widget := Widget{
|
||||
TextWidget: view.NewTextWidget(app, settings.common, false),
|
||||
|
||||
objects: settings.objects,
|
||||
title: settings.title,
|
||||
kubeconfig: settings.kubeconfig,
|
||||
namespaces: settings.namespaces,
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
widget.View.SetWrap(true)
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
// Refresh executes the command and updates the view with the results
|
||||
func (widget *Widget) Refresh() {
|
||||
|
||||
title := widget.generateTitle()
|
||||
|
||||
client := widget.getInstance()
|
||||
|
||||
var content string
|
||||
|
||||
// Debug Info
|
||||
//content += fmt.Sprintf("Namespaces: %q %d\n", widget.namespaces, len(widget.namespaces))
|
||||
//content += fmt.Sprintf("Objects: %q %d\n\n", widget.objects, len(widget.objects))
|
||||
|
||||
if !utils.DoesNotInclude(widget.objects, "nodes") {
|
||||
nodeList, nodeError := client.getNodes()
|
||||
if nodeError != nil {
|
||||
widget.Redraw(title, "[red] Error getting node data [white]\n", true)
|
||||
return
|
||||
}
|
||||
content += "[red]Nodes[white]\n"
|
||||
for _, node := range nodeList {
|
||||
content += fmt.Sprintf("%s\n", node)
|
||||
}
|
||||
content += "\n"
|
||||
}
|
||||
|
||||
if !utils.DoesNotInclude(widget.objects, "deployments") {
|
||||
deploymentList, deploymentError := client.getDeployments(widget.namespaces)
|
||||
if deploymentError != nil {
|
||||
widget.Redraw(title, "[red] Error getting deployment data [white]\n", true)
|
||||
return
|
||||
}
|
||||
content += "[red]Deployments[white]\n"
|
||||
for _, deployment := range deploymentList {
|
||||
content += fmt.Sprintf("%s\n", deployment)
|
||||
}
|
||||
content += "\n"
|
||||
}
|
||||
|
||||
if !utils.DoesNotInclude(widget.objects, "pods") {
|
||||
podList, podError := client.getPods(widget.namespaces)
|
||||
if podError != nil {
|
||||
widget.Redraw(title, "[red] Error getting pod data [white]\n", false)
|
||||
return
|
||||
}
|
||||
content += "[red]Pods[white]\n"
|
||||
for _, pod := range podList {
|
||||
content += fmt.Sprintf("%s\n", pod)
|
||||
}
|
||||
content += "\n"
|
||||
}
|
||||
|
||||
widget.Redraw(title, content, false)
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
// generateTitle generates a title for the widget
|
||||
func (widget *Widget) generateTitle() string {
|
||||
if len(widget.title) != 0 {
|
||||
return widget.title
|
||||
}
|
||||
title := "Kube"
|
||||
|
||||
if len(widget.namespaces) == 1 {
|
||||
title += fmt.Sprintf(" - Namespace: %s", widget.namespaces[0])
|
||||
} else if len(widget.namespaces) > 1 {
|
||||
title += fmt.Sprintf(" - Namespaces: %q", widget.namespaces)
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
// getPods returns a slice of pod strings
|
||||
func (client *clientInstance) getPods(namespaces []string) ([]string, error) {
|
||||
var podList []string
|
||||
if len(namespaces) != 0 {
|
||||
for _, namespace := range namespaces {
|
||||
pods, err := client.Client.CoreV1().Pods(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
var podString string
|
||||
status := pod.Status.Phase
|
||||
name := pod.ObjectMeta.Name
|
||||
if len(namespaces) == 1 {
|
||||
podString = fmt.Sprintf("%-50s %s", name, status)
|
||||
} else {
|
||||
podString = fmt.Sprintf("%-20s %-50s %s", namespace, name, status)
|
||||
}
|
||||
podList = append(podList, podString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pods, err := client.Client.CoreV1().Pods("").List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pod := range pods.Items {
|
||||
podString := fmt.Sprintf("%-20s %-50s %s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, pod.Status.Phase)
|
||||
podList = append(podList, podString)
|
||||
}
|
||||
}
|
||||
|
||||
return podList, nil
|
||||
}
|
||||
|
||||
// get Deployments returns a string slice of pod strings
|
||||
func (client *clientInstance) getDeployments(namespaces []string) ([]string, error) {
|
||||
var deploymentList []string
|
||||
if len(namespaces) != 0 {
|
||||
for _, namespace := range namespaces {
|
||||
deployments, err := client.Client.AppsV1().Deployments(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, deployment := range deployments.Items {
|
||||
var deployString string
|
||||
if len(namespaces) == 1 {
|
||||
deployString = fmt.Sprintf("%-50s", deployment.ObjectMeta.Name)
|
||||
} else {
|
||||
deployString = fmt.Sprintf("%-20s %-50s", deployment.ObjectMeta.Namespace, deployment.ObjectMeta.Name)
|
||||
}
|
||||
deploymentList = append(deploymentList, deployString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deployments, err := client.Client.AppsV1().Deployments("").List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, deployment := range deployments.Items {
|
||||
deployString := fmt.Sprintf("%-20s %-50s", deployment.ObjectMeta.Namespace, deployment.ObjectMeta.Name)
|
||||
deploymentList = append(deploymentList, deployString)
|
||||
}
|
||||
}
|
||||
return deploymentList, nil
|
||||
}
|
||||
|
||||
// getNodes returns a string slice of nodes
|
||||
func (client *clientInstance) getNodes() ([]string, error) {
|
||||
var nodeList []string
|
||||
|
||||
nodes, err := client.Client.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, node := range nodes.Items {
|
||||
var nodeStatus string
|
||||
for _, condition := range node.Status.Conditions {
|
||||
if condition.Reason == "KubeletReady" {
|
||||
if condition.Status == "True" {
|
||||
nodeStatus = "Ready"
|
||||
} else if condition.Reason == "False" {
|
||||
nodeStatus = "NotReady"
|
||||
} else {
|
||||
nodeStatus = "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeString := fmt.Sprintf("%-50s %s", node.ObjectMeta.Name, nodeStatus)
|
||||
nodeList = append(nodeList, nodeString)
|
||||
}
|
||||
return nodeList, nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user