mirror of
https://github.com/turbot/steampipe.git
synced 2026-05-13 16:00:12 -04:00
178 lines
5.4 KiB
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
|
|
}
|