1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Merge pull request #526 from FairwindsOps/sudermanjr/kubernetes

Kubernetes Addon
This commit is contained in:
Chris Cummer
2019-08-06 16:11:08 -07:00
committed by GitHub
9 changed files with 456 additions and 42 deletions

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: [nodes, 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
}

View File

@@ -0,0 +1,58 @@
package kubernetes
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_generateTitle(t *testing.T) {
type fields struct {
title string
namespaces []string
}
testCases := []struct {
name string
fields fields
want string
}{
{
name: "No Namespaces",
fields: fields{
namespaces: []string{},
},
want: "Kube",
},
{
name: "One Namespace",
fields: fields{
namespaces: []string{"some-namespace"},
},
want: "Kube - Namespace: some-namespace",
},
{
name: "Multiple Namespaces",
fields: fields{
namespaces: []string{"ns1", "ns2"},
},
want: `Kube - Namespaces: ["ns1" "ns2"]`,
},
{
name: "Explicit Title Set",
fields: fields{
namespaces: []string{},
title: "Test Explicit Title",
},
want: "Test Explicit Title",
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
widget := &Widget{
title: tt.fields.title,
namespaces: tt.fields.namespaces,
}
assert.Equal(t, tt.want, widget.generateTitle())
})
}
}