205 lines
4.7 KiB
Go
205 lines
4.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Authors of K9s
|
|
|
|
package render
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/derailed/k9s/internal/client"
|
|
"github.com/derailed/popeye/pkg/config"
|
|
"github.com/derailed/tcell/v2"
|
|
"github.com/derailed/tview"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
)
|
|
|
|
// Popeye renders a sanitizer to screen.
|
|
type Popeye struct {
|
|
Base
|
|
}
|
|
|
|
// ColorerFunc colors a resource row.
|
|
func (Popeye) ColorerFunc() ColorerFunc {
|
|
return func(ns string, h Header, re RowEvent) tcell.Color {
|
|
c := DefaultColorer(ns, h, re)
|
|
|
|
warnCol := h.IndexOf("WARNING", true)
|
|
status, _ := strconv.Atoi(strings.TrimSpace(re.Row.Fields[warnCol]))
|
|
if status > 0 {
|
|
c = tcell.ColorOrange
|
|
}
|
|
errCol := h.IndexOf("ERROR", true)
|
|
status, _ = strconv.Atoi(strings.TrimSpace(re.Row.Fields[errCol]))
|
|
if status > 0 {
|
|
c = ErrColor
|
|
}
|
|
return c
|
|
}
|
|
}
|
|
|
|
// Header returns a header row.
|
|
func (Popeye) Header(ns string) Header {
|
|
return Header{
|
|
HeaderColumn{Name: "RESOURCE"},
|
|
HeaderColumn{Name: "SCORE%", Align: tview.AlignRight},
|
|
HeaderColumn{Name: "SCANNED", Align: tview.AlignRight},
|
|
HeaderColumn{Name: "ERROR", Align: tview.AlignRight},
|
|
HeaderColumn{Name: "WARNING", Align: tview.AlignRight},
|
|
HeaderColumn{Name: "INFO", Align: tview.AlignRight},
|
|
HeaderColumn{Name: "OK", Align: tview.AlignRight},
|
|
}
|
|
}
|
|
|
|
// Render renders a K8s resource to screen.
|
|
func (Popeye) Render(o interface{}, ns string, r *Row) error {
|
|
s, ok := o.(Section)
|
|
if !ok {
|
|
return fmt.Errorf("expected Section, but got %T", o)
|
|
}
|
|
|
|
r.ID = client.FQN(ns, s.Title)
|
|
r.Fields = append(r.Fields,
|
|
s.Title,
|
|
strconv.Itoa(s.Tally.Score()),
|
|
strconv.Itoa(s.Tally.OK+s.Tally.Info+s.Tally.Warning+s.Tally.Error),
|
|
strconv.Itoa(s.Tally.Error),
|
|
strconv.Itoa(s.Tally.Warning),
|
|
strconv.Itoa(s.Tally.Info),
|
|
strconv.Itoa(s.Tally.OK),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Helpers...
|
|
|
|
type (
|
|
// Builder represents a popeye report.
|
|
Builder struct {
|
|
Report Report `json:"popeye" yaml:"popeye"`
|
|
}
|
|
|
|
// Report represents the output of a sanitization pass.
|
|
Report struct {
|
|
Score int `json:"score" yaml:"score"`
|
|
Grade string `json:"grade" yaml:"grade"`
|
|
Sections Sections `json:"sanitizers,omitempty" yaml:"sanitizers,omitempty"`
|
|
}
|
|
|
|
// Sections represents a collection of sections.
|
|
Sections []Section
|
|
|
|
// Section represents a sanitizer pass.
|
|
Section struct {
|
|
Title string `json:"sanitizer" yaml:"sanitizer"`
|
|
GVR string `yaml:"gvr" json:"gvr"`
|
|
Tally *Tally `json:"tally" yaml:"tally"`
|
|
Outcome Outcome `json:"issues,omitempty" yaml:"issues,omitempty"`
|
|
}
|
|
|
|
// Outcome represents a classification of reports outcome.
|
|
Outcome map[string]Issues
|
|
|
|
// Issues represents a collection of issues.
|
|
Issues []Issue
|
|
|
|
// Issue represents a sanitization issue.
|
|
Issue struct {
|
|
Group string `yaml:"group" json:"group"`
|
|
GVR string `yaml:"gvr" json:"gvr"`
|
|
Level config.Level `yaml:"level" json:"level"`
|
|
Message string `yaml:"message" json:"message"`
|
|
}
|
|
|
|
// Tally tracks a section scores.
|
|
Tally struct {
|
|
OK, Info, Warning, Error int
|
|
Count int
|
|
}
|
|
)
|
|
|
|
// Sum sums up tally counts.
|
|
func (t *Tally) Sum() int {
|
|
return t.OK + t.Info + t.Warning + t.Error
|
|
}
|
|
|
|
// Score returns the overall sections score in percent.
|
|
func (t *Tally) Score() int {
|
|
oks := t.OK + t.Info
|
|
return toPerc(float64(oks), float64(oks+t.Warning+t.Error))
|
|
}
|
|
|
|
func toPerc(v1, v2 float64) int {
|
|
if v2 == 0 {
|
|
return 0
|
|
}
|
|
return int(math.Floor((v1 / v2) * 100))
|
|
}
|
|
|
|
// Len returns a section length.
|
|
func (s Sections) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
// Swap swaps values.
|
|
func (s Sections) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
// Less compares section scores.
|
|
func (s Sections) Less(i, j int) bool {
|
|
t1, t2 := s[i].Tally, s[j].Tally
|
|
return t1.Score() < t2.Score()
|
|
}
|
|
|
|
// GetObjectKind returns a schema object.
|
|
func (Section) GetObjectKind() schema.ObjectKind {
|
|
return nil
|
|
}
|
|
|
|
// DeepCopyObject returns a container copy.
|
|
func (s Section) DeepCopyObject() runtime.Object {
|
|
return s
|
|
}
|
|
|
|
// MaxSeverity gather the max severity in a collection of issues.
|
|
func (s Section) MaxSeverity() config.Level {
|
|
max := config.OkLevel
|
|
for _, issues := range s.Outcome {
|
|
m := issues.MaxSeverity()
|
|
if m > max {
|
|
max = m
|
|
}
|
|
}
|
|
|
|
return max
|
|
}
|
|
|
|
// MaxSeverity gather the max severity in a collection of issues.
|
|
func (i Issues) MaxSeverity() config.Level {
|
|
max := config.OkLevel
|
|
for _, is := range i {
|
|
if is.Level > max {
|
|
max = is.Level
|
|
}
|
|
}
|
|
|
|
return max
|
|
}
|
|
|
|
// CountSeverity counts severity level instances.
|
|
func (i Issues) CountSeverity(l config.Level) int {
|
|
var count int
|
|
for _, is := range i {
|
|
if is.Level == l {
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|