1
0
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:
Andrew Suderman 2019-08-05 17:44:34 -06:00
parent 97ca61ac97
commit da0e1dbd5b
No known key found for this signature in database
GPG Key ID: 9C9CAE7EC2133DB7
6 changed files with 303 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

View 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

View File

@ -27,6 +27,7 @@ import (
"github.com/wtfutil/wtf/modules/ipaddresses/ipinfo" "github.com/wtfutil/wtf/modules/ipaddresses/ipinfo"
"github.com/wtfutil/wtf/modules/jenkins" "github.com/wtfutil/wtf/modules/jenkins"
"github.com/wtfutil/wtf/modules/jira" "github.com/wtfutil/wtf/modules/jira"
"github.com/wtfutil/wtf/modules/kubernetes"
"github.com/wtfutil/wtf/modules/logger" "github.com/wtfutil/wtf/modules/logger"
"github.com/wtfutil/wtf/modules/mercurial" "github.com/wtfutil/wtf/modules/mercurial"
"github.com/wtfutil/wtf/modules/nbascore" "github.com/wtfutil/wtf/modules/nbascore"
@ -144,6 +145,9 @@ func MakeWidget(
case "jira": case "jira":
settings := jira.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := jira.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = jira.NewWidget(app, pages, settings) widget = jira.NewWidget(app, pages, settings)
case "kubernetes":
settings := kubernetes.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = kubernetes.NewWidget(app, settings)
case "logger": case "logger":
settings := logger.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := logger.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = logger.NewWidget(app, settings) widget = logger.NewWidget(app, settings)

View 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
}

View 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
}

View 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
}