package render import ( "fmt" "strings" "github.com/gdamore/tcell/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) const allVerbs = "*" var ( k8sVerbs = []string{ "get", "list", "watch", "create", "patch", "update", "delete", "deletecollection", } httpTok8sVerbs = map[string]string{ "post": "create", "put": "update", } ) // Rbac renders a rbac to screen. type Rbac struct{} // ColorerFunc colors a resource row. func (Rbac) ColorerFunc() ColorerFunc { return func(_ string, _ Header, _re RowEvent) tcell.Color { return tcell.ColorMediumSpringGreen } } // Header returns a header row. func (Rbac) Header(ns string) Header { h := make(Header, 0, 10) h = append(h, HeaderColumn{Name: "NAME"}, HeaderColumn{Name: "APIGROUP"}, ) h = append(h, rbacVerbHeader()...) return append(h, HeaderColumn{Name: "VALID", Wide: true}) } // Render renders a K8s resource to screen. func (r Rbac) Render(o interface{}, ns string, ro *Row) error { p, ok := o.(PolicyRes) if !ok { return fmt.Errorf("expecting RuleRes but got %T", o) } ro.ID = p.Resource ro.Fields = make(Fields, 0, len(r.Header(ns))) ro.Fields = append(ro.Fields, cleanseResource(p.Resource), p.Group, ) ro.Fields = append(ro.Fields, asVerbs(p.Verbs)...) ro.Fields = append(ro.Fields, "") return nil } // ---------------------------------------------------------------------------- // Helpers... func asVerbs(verbs []string) []string { const ( verbLen = 4 unknownLen = 30 ) r := make([]string, 0, len(k8sVerbs)+1) for _, v := range k8sVerbs { r = append(r, toVerbIcon(hasVerb(verbs, v))) } var unknowns []string for _, v := range verbs { if hv, ok := httpTok8sVerbs[v]; ok { v = hv } if !hasVerb(k8sVerbs, v) && v != allVerbs { unknowns = append(unknowns, v) } } return append(r, Truncate(strings.Join(unknowns, ","), unknownLen)) } func toVerbIcon(ok bool) string { if ok { return "[green::b] ✓ [::]" } return "[orangered::b] × [::]" } func hasVerb(verbs []string, verb string) bool { if len(verbs) == 1 && verbs[0] == allVerbs { return true } for _, v := range verbs { if hv, ok := httpTok8sVerbs[v]; ok { if hv == verb { return true } } if v == verb { return true } } return false } // RuleRes represents an rbac rule. type RuleRes struct { Resource, Group string ResourceName string NonResourceURL string Verbs []string } // NewRuleRes returns a new rule. func NewRuleRes(res, grp string, vv []string) RuleRes { return RuleRes{ Resource: res, Group: grp, Verbs: vv, } } // GetObjectKind returns a schema object. func (r RuleRes) GetObjectKind() schema.ObjectKind { return nil } // DeepCopyObject returns a container copy. func (r RuleRes) DeepCopyObject() runtime.Object { return r } // Rules represents a collection of rules. type Rules []RuleRes // Upsert adds a new rule. func (rr Rules) Upsert(r RuleRes) Rules { idx, ok := rr.find(r.Resource) if !ok { return append(rr, r) } rr[idx] = r return rr } // Find locates a row by id. Returns false is not found. func (rr Rules) find(res string) (int, bool) { for i, r := range rr { if r.Resource == res { return i, true } } return 0, false }