bergerx/kubectl-status

View on GitHub
pkg/plugin/renderable.go

Summary

Maintainability
A
0 mins
Test Coverage
package plugin

import (
    "bytes"
    "fmt"
    "io"

    "github.com/fatih/color"
    "github.com/spf13/viper"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/klog/v2"
    kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"

    "github.com/bergerx/kubectl-status/pkg/input"
)

func newRenderableObject(obj map[string]interface{}, engine *renderEngine, repo *input.ResourceRepo) RenderableObject {
    r := RenderableObject{
        Unstructured: unstructured.Unstructured{Object: obj},
        engine:       engine,
        repo:         repo,
        Config:       viper.GetViper(),
    }
    return r
}

// RenderableObject is the object passed to the templates, also provides methods to run queries against Kubernetes API.
// It is an unstructured.Unstructured (so it has the Object field that keeps the Object) but there a numerous helper
// methods that already helps with the templates.
type RenderableObject struct {
    unstructured.Unstructured
    engine *renderEngine
    repo   *input.ResourceRepo
    Config *viper.Viper
}

// KStatus return a Result object of kstatus for the object.
func (r RenderableObject) KStatus() *kstatus.Result {
    result, err := kstatus.Compute(&r.Unstructured)
    if err != nil {
        klog.V(2).ErrorS(err, "kstatus.Compute failed", "r", r)
    }
    return result
}

func (r RenderableObject) newRenderableObject(obj map[string]interface{}) RenderableObject {
    return newRenderableObject(obj, r.engine, r.repo)
}

func (r RenderableObject) String() string {
    kindAndName := fmt.Sprintf("%s/%s", r.Kind(), r.Name())
    if namespace := r.Namespace(); namespace != "" {
        kindAndName = fmt.Sprintf("%s[%s]", kindAndName, namespace)
    }
    return kindAndName
}

func (r RenderableObject) Kind() (kind string) {
    if x := r.Object["kind"]; x != nil {
        kind = x.(string)
    }
    return
}

func (r RenderableObject) Spec() (spec map[string]interface{}) {
    if x := r.Object["spec"]; x != nil {
        spec = x.(map[string]interface{})
    }
    return
}

func (r RenderableObject) Status() (status map[string]interface{}) {
    if x := r.Object["status"]; x != nil {
        status = x.(map[string]interface{})
    }
    return
}

func (r RenderableObject) Metadata() (metadata map[string]interface{}) {
    if x := r.Object["metadata"]; x != nil {
        metadata = x.(map[string]interface{})
    }
    return
}

func (r RenderableObject) Annotations() (annotations map[string]interface{}) {
    if x := r.Metadata()["annotations"]; x != nil {
        annotations = x.(map[string]interface{})
    }
    return
}

func (r RenderableObject) Labels() (labels map[string]interface{}) {
    if x := r.Metadata()["labels"]; x != nil {
        labels = x.(map[string]interface{})
    }
    return
}

func (r RenderableObject) Name() string {
    return r.GetName()
}

func (r RenderableObject) Namespace() string {
    return r.GetNamespace()
}

func (r RenderableObject) StatusConditions() (conditions []interface{}) {
    if x := r.Status()["conditions"]; x != nil {
        conditions = x.([]interface{})
    }
    return
}

func (r RenderableObject) render(wr io.Writer) error {
    klog.V(5).InfoS("called render, calling findTemplateName", "r", r)
    templateName := findTemplateName(r.engine.Template, r.Kind())
    klog.V(5).InfoS("calling executeTemplate on renderable", "r", r, "templateName", templateName)
    err := r.executeTemplate(wr, templateName, r)
    if err != nil {
        klog.V(3).ErrorS(err, "error on executeTemplate", "r", r)
    }
    return err
}

func (r RenderableObject) renderString() (string, error) {
    klog.V(5).InfoS("called renderString", "r", r)
    var buffer bytes.Buffer
    err := r.render(&buffer)
    return buffer.String(), err
}

func (r RenderableObject) renderTemplate(templateName string, data interface{}) (string, error) {
    var buffer bytes.Buffer
    klog.V(5).InfoS("called renderTemplate, calling ExecuteTemplate",
        "r", r, "templateName", templateName, "data", data)
    err := r.executeTemplate(&buffer, templateName, data)
    if err != nil {
        klog.V(3).ErrorS(err, "error executing template",
            "r", r, "templateName", templateName)
    }
    return buffer.String(), err
}

func (r RenderableObject) executeTemplate(wr io.Writer, name string, data any) error {
    target, ok := data.(RenderableObject)
    if ok && target.Kind() == name && renderedUIDs.checkAdd(target.GetUID()) && !viper.GetBool("watching") && !viper.GetBool("test-hack") {
        klog.V(3).InfoS("skip rendering of the RenderableObject as its already rendered",
            "r", r, "templateName", name)
        _, _ = color.New(color.FgWhite).Fprintf(wr, "%s is already printed", target.String())
        return nil
    }
    return r.engine.ExecuteTemplate(wr, name, data)
}

type uidSet map[types.UID]struct{}

func (s uidSet) checkAdd(uid types.UID) bool {
    _, exists := s[uid]
    if !exists {
        s[uid] = struct{}{}
    }
    return exists
}

var renderedUIDs = make(uidSet)