Files
steampipe/pkg/control/controldisplay/group.go

178 lines
5.4 KiB
Go

package controldisplay
import (
"fmt"
"log"
"strings"
"github.com/turbot/steampipe/pkg/control/controlexecute"
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
)
type GroupRenderer struct {
group *controlexecute.ResultGroup
// screen width
width int
maxFailedControls int
maxTotalControls int
resultTree *controlexecute.ExecutionTree
lastChild bool
parent *GroupRenderer
}
func NewGroupRenderer(group *controlexecute.ResultGroup, parent *GroupRenderer, maxFailedControls, maxTotalControls int, resultTree *controlexecute.ExecutionTree, width int) *GroupRenderer {
r := &GroupRenderer{
group: group,
parent: parent,
resultTree: resultTree,
maxFailedControls: maxFailedControls,
maxTotalControls: maxTotalControls,
width: width,
}
r.lastChild = r.isLastChild(group)
return r
}
// are we the last child of our parent?
// this affects the tree rendering
func (r GroupRenderer) isLastChild(group *controlexecute.ResultGroup) bool {
if group.Parent == nil || group.Parent.GroupItem == nil {
return true
}
siblings := group.Parent.GroupItem.GetChildren()
// get the name of the last sibling which has controls (or is a control)
var finalSiblingName string
for _, s := range siblings {
if b, ok := s.(*modconfig.Benchmark); ok {
// find the result group for this benchmark and see if it has controls
resultGroup := r.resultTree.Root.GetChildGroupByName(b.Name())
// if the result group has not controls, we will not find it in the result tree
if resultGroup == nil || resultGroup.ControlRunCount() == 0 {
continue
}
}
// store the name of this sibling
finalSiblingName = s.Name()
}
res := group.GroupItem.Name() == finalSiblingName
return res
}
// the indent for blank lines
// same as for (not last) children
func (r GroupRenderer) blankLineIndent() string {
return r.childIndent()
}
// the indent for group heading
func (r GroupRenderer) headingIndent() string {
// if this is the first displayed node, no indent
if r.parent == nil || r.parent.group.GroupId == controlexecute.RootResultGroupName {
return ""
}
// as our parent for the indent for a group
i := r.parent.childGroupIndent()
return i
}
// the indent for child groups/controls (which are not the final child)
// include the tree '|'
func (r GroupRenderer) childIndent() string {
return r.parentIndent() + "| "
}
// the indent for the FINAL child groups/controls
// just a space
func (r GroupRenderer) lastChildIndent() string {
return r.parentIndent() + " "
}
// the indent for child groups - our parent indent with the group expander "+ "
func (r GroupRenderer) childGroupIndent() string {
return r.parentIndent() + "+ "
}
// get the indent inherited from our parent
// - this will depend on whether we are our parents last child
func (r GroupRenderer) parentIndent() string {
if r.parent == nil || r.parent.group.GroupId == controlexecute.RootResultGroupName {
return ""
}
if r.lastChild {
return r.parent.lastChildIndent()
}
return r.parent.childIndent()
}
func (r GroupRenderer) Render() string {
if r.width <= 0 {
// this should never happen, since the minimum width is set by the formatter
log.Printf("[WARN] group renderer has width of %d\n", r.width)
return ""
}
if r.group.GroupId == controlexecute.RootResultGroupName {
return r.renderRootResultGroup()
}
groupHeadingRenderer := NewGroupHeadingRenderer(
r.group.Title,
r.group.Summary.Status.FailedCount(),
r.group.Summary.Status.TotalCount(),
r.maxFailedControls,
r.maxTotalControls,
r.width,
r.headingIndent())
// render this group header
tableStrings := append([]string{},
groupHeadingRenderer.Render(),
// newline after group
fmt.Sprintf("%s", ControlColors.Indent(r.blankLineIndent())))
// now render the group children, in the order they are specified
childStrings := r.renderChildren()
tableStrings = append(tableStrings, childStrings...)
return strings.Join(tableStrings, "\n")
}
// for root result group, there will either be one or more groups, or one or more control runs
// there will be no order specified so just loop through them
func (r GroupRenderer) renderRootResultGroup() string {
var resultStrings = make([]string, len(r.group.Groups)+len(r.group.ControlRuns))
for i, group := range r.group.Groups {
groupRenderer := NewGroupRenderer(group, &r, r.maxFailedControls, r.maxTotalControls, r.resultTree, r.width)
resultStrings[i] = groupRenderer.Render()
}
for i, run := range r.group.ControlRuns {
controlRenderer := NewControlRenderer(run, &r)
resultStrings[i] = controlRenderer.Render()
}
return strings.Join(resultStrings, "\n")
}
// render the children of this group, in the order they are specified in the hcl
func (r GroupRenderer) renderChildren() []string {
children := r.group.GroupItem.GetChildren()
var childStrings []string
for _, child := range children {
if control, ok := child.(*modconfig.Control); ok {
// get Result group with a matching name
if run := r.group.GetControlRunByName(control.Name()); run != nil {
controlRenderer := NewControlRenderer(run, &r)
childStrings = append(childStrings, controlRenderer.Render())
}
} else {
if childGroup := r.group.GetGroupByName(child.Name()); childGroup != nil {
groupRenderer := NewGroupRenderer(childGroup, &r, r.maxFailedControls, r.maxTotalControls, r.resultTree, r.width)
childStrings = append(childStrings, groupRenderer.Render())
}
}
}
return childStrings
}