Skip to content

Commit

Permalink
Merge pull request #197 from homeport/qu1queee/custom_watch
Browse files Browse the repository at this point in the history
watch: Add support for different resources
  • Loading branch information
qu1queee authored Oct 10, 2019
2 parents 9822012 + 184de7a commit 7cfaee1
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 6 deletions.
155 changes: 149 additions & 6 deletions internal/cmd/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package cmd

import (
"errors"
"fmt"
"sort"
"strings"
Expand All @@ -37,6 +38,8 @@ import (
var watchCmdSettings struct {
interval int
namespaces []string
resource string
crd string
}

// watchCmd represents the top command
Expand All @@ -50,6 +53,10 @@ var watchCmd = &cobra.Command{
return err
}

if watchCmdSettings.crd != "" && watchCmdSettings.resource != "" {
return errors.New("--resource and --crd flags cannot be specified simultaneously")
}

term.HideCursor()
defer term.ShowCursor()

Expand All @@ -72,15 +79,52 @@ var watchCmd = &cobra.Command{

func init() {
rootCmd.AddCommand(watchCmd)

watchCmd.PersistentFlags().IntVarP(&watchCmdSettings.interval, "interval", "i", 2, "interval between measurements in seconds")
watchCmd.PersistentFlags().StringVarP(&watchCmdSettings.resource, "resource", "r", "", "resource to watch (default to pods)")
watchCmd.PersistentFlags().StringSliceVarP(&watchCmdSettings.namespaces, "namespace", "n", []string{}, "comma separated list of namespaces to filter (default is to use all namespaces")
watchCmd.PersistentFlags().StringVarP(&watchCmdSettings.crd, "crd", "c", "", "crd to watch, based on the singular or short-name of the resource")
}

func printWatchList(hvnr havener.Havener) error {
func printWatchList(hvnr havener.Havener) (err error) {
var out string

if watchCmdSettings.crd != "" {
out, err = generateCRDTable(hvnr)
if err != nil {
return err
}
} else {
switch watchCmdSettings.resource {
case "pods":
out, err = generatePodsTable(hvnr)
if err != nil {
return err
}
case "secrets":
out, err = generateSecretsTable(hvnr)
if err != nil {
return err
}
case "configmaps":
out, err = generateCMTable(hvnr)
if err != nil {
return err
}
default:
out, err = generatePodsTable(hvnr)
if err != nil {
return err
}
}
}
print("\x1b[H", "\x1b[2J", out)
return nil
}

func generatePodsTable(hvnr havener.Havener) (string, error) {
pods, err := hvnr.ListPods(watchCmdSettings.namespaces...)
if err != nil {
return err
return "", err
}

sort.Slice(pods, func(i, j int) bool {
Expand Down Expand Up @@ -175,13 +219,112 @@ func printWatchList(hvnr havener.Havener) error {
table,
neat.CustomSeparator(" "),
)
if err != nil {
return "", err
}
return out, nil
}

func generateSecretsTable(hvnr havener.Havener) (secResult string, err error) {
var tableSec = [][]string{}

secrets, err := hvnr.ListSecrets(watchCmdSettings.namespaces...)
if err != nil {
return err
return "", err
}

print("\x1b[H", "\x1b[2J", out)
return nil
for _, secret := range secrets {
styleOptions := []bunt.StyleOption{}

age := humanReadableDuration(time.Now().Sub(secret.CreationTimestamp.Time))

tableSec = append(tableSec, []string{
bunt.Style(secret.Namespace, styleOptions...),
bunt.Style(secret.Name, styleOptions...),
bunt.Style(age, styleOptions...),
})
}

secResult, err = renderBoxWithTable(
bunt.Sprintf("Secrets running in cluster _%s_", hvnr.ClusterName()),
[]string{"Namespace", "Name", "Age"},
tableSec,
neat.CustomSeparator(" "),
)
if err != nil {
return "", err
}

return secResult, nil
}

func generateCMTable(hvnr havener.Havener) (cmResult string, err error) {

var tableSec = [][]string{}

configMaps, err := hvnr.ListConfigMaps(watchCmdSettings.namespaces...)
if err != nil {
return "", err
}

for _, cm := range configMaps {
styleOptions := []bunt.StyleOption{}
age := humanReadableDuration(time.Now().Sub(cm.CreationTimestamp.Time))
tableSec = append(tableSec, []string{
bunt.Style(cm.Namespace, styleOptions...),
bunt.Style(cm.Name, styleOptions...),
bunt.Style(age, styleOptions...),
})
}

cmResult, err = renderBoxWithTable(
bunt.Sprintf("Configmaps running in cluster _%s_", hvnr.ClusterName()),
[]string{"Namespace", "Name", "Age"},
tableSec,
neat.CustomSeparator(" "),
)
if err != nil {
return "", err
}

return cmResult, nil
}

func generateCRDTable(hvnr havener.Havener) (string, error) {

var (
tableSec = [][]string{}
)
bdplList, err := hvnr.ListCustomResourceDefinition(watchCmdSettings.crd)
if err != nil {
return "", err
}

for _, bdpl := range bdplList {
styleOptions := []bunt.StyleOption{}

age := humanReadableDuration(
time.Now().Sub(
bdpl.GetCreationTimestamp().Time,
),
)
tableSec = append(tableSec, []string{
bunt.Style(bdpl.GetNamespace(), styleOptions...),
bunt.Style(bdpl.GetName(), styleOptions...),
bunt.Style(age, styleOptions...),
})
}
outBDPL, err := renderBoxWithTable(
bunt.Sprintf("%s running in cluster _%s_", watchCmdSettings.crd, hvnr.ClusterName()),
[]string{"Namespace", "Name", "Age"},
tableSec,
neat.CustomSeparator(" "),
)

if err != nil {
return "", err
}
return outBDPL, nil
}

func humanReadableNamespaceCategory(pod corev1.Pod) string {
Expand Down
30 changes: 30 additions & 0 deletions pkg/havener/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ import (
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/gonvenience/wrap"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc" //from https://github.com/kubernetes/client-go/issues/345
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -170,3 +173,30 @@ func clusterName() (string, error) {

return "", fmt.Errorf("unable to determine cluster name based on Kubernetes configuration")
}

func apiCRDResourceExist(arl []*metav1.APIResourceList, crdName string) (bool, schema.GroupVersionResource) {
for _, ar := range arl {
// Look for a CRD based on it´s singular or
// different short names.
for _, r := range ar.APIResources {
if crdName == r.SingularName || containsItem(r.ShortNames, crdName) {
groupVersion := strings.Split(ar.GroupVersion, "/")
return true, schema.GroupVersionResource{
Group: groupVersion[0],
Version: groupVersion[1],
Resource: r.Name,
}
}
}
}
return false, schema.GroupVersionResource{}
}

func containsItem(l []string, s string) bool {
for _, a := range l {
if a == s {
return true
}
}
return false
}
4 changes: 4 additions & 0 deletions pkg/havener/havener.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/gonvenience/wrap"
"golang.org/x/sync/syncmap"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
Expand Down Expand Up @@ -86,6 +87,9 @@ type Havener interface {

PodExec(pod *corev1.Pod, container string, command []string, stdin io.Reader, stdout io.Writer, stderr io.Writer, tty bool) error
NodeExec(node corev1.Node, containerImage string, timeoutSeconds int, command []string, stdin io.Reader, stdout io.Writer, stderr io.Writer, tty bool) error
ListSecrets(namespaces ...string) ([]*corev1.Secret, error)
ListConfigMaps(namespaces ...string) ([]*corev1.ConfigMap, error)
ListCustomResourceDefinition(string) ([]unstructured.Unstructured, error)
}

// NewHavener returns a new Havener handle to perform cluster actions
Expand Down
78 changes: 78 additions & 0 deletions pkg/havener/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
package havener

import (
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/gonvenience/wrap"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
)

Expand Down Expand Up @@ -129,6 +134,79 @@ func (h *Hvnr) ListPods(namespaces ...string) (result []*corev1.Pod, err error)
return result, nil
}

// ListSecrets lists all secrets in the given namespaces, if no namespace is given,
// then all namespaces currently available in the cluster will be used
func (h *Hvnr) ListSecrets(namespaces ...string) (result []*corev1.Secret, err error) {
if len(namespaces) == 0 {
namespaces, err = ListNamespaces(h.client)
if err != nil {
return nil, err
}
}

for _, namespace := range namespaces {
listResp, err := h.client.CoreV1().Secrets(namespace).List(metav1.ListOptions{})
if err != nil {
return nil, err
}

for i := range listResp.Items {
result = append(result, &listResp.Items[i])
}
}

return result, nil
}

// ListConfigMaps lists all confimaps in the given namespaces, if no namespace is given,
// then all namespaces currently available in the cluster will be used
func (h *Hvnr) ListConfigMaps(namespaces ...string) (result []*corev1.ConfigMap, err error) {
if len(namespaces) == 0 {
namespaces, err = ListNamespaces(h.client)
if err != nil {
return nil, err
}
}

for _, namespace := range namespaces {
listResp, err := h.client.CoreV1().ConfigMaps(namespace).List(metav1.ListOptions{})
if err != nil {
return nil, err
}

for i := range listResp.Items {
result = append(result, &listResp.Items[i])
}
}

return result, nil
}

// ListCustomResourceDefinition lists all instances of an specific CRD
func (h *Hvnr) ListCustomResourceDefinition(crdName string) (result []unstructured.Unstructured, err error) {

var runtimeClassGVR schema.GroupVersionResource

apiResourceList, err := h.client.Discovery().ServerResources()
if err != nil {
return nil, err
}

crdExist, runtimeClassGVR := apiCRDResourceExist(apiResourceList, crdName)

if crdExist {
client, _ := dynamic.NewForConfig(h.restconfig)
list, _ := client.Resource(runtimeClassGVR).List(metav1.ListOptions{})

for i := range list.Items {
result = append(result, list.Items[i])
}
return result, nil
}

return result, fmt.Errorf("desired resource %s, was not found", crdName)
}

// ListNodes lists all nodes of the cluster
// Deprecated: Use Havener interface function ListNodeNames instead
func ListNodes(client kubernetes.Interface) ([]string, error) {
Expand Down

0 comments on commit 7cfaee1

Please sign in to comment.