github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/control/controldisplay/group.go (about)

     1  package controldisplay
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/turbot/steampipe/pkg/control/controlexecute"
     9  	"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
    10  )
    11  
    12  type GroupRenderer struct {
    13  	group *controlexecute.ResultGroup
    14  	// screen width
    15  	width             int
    16  	maxFailedControls int
    17  	maxTotalControls  int
    18  	resultTree        *controlexecute.ExecutionTree
    19  	lastChild         bool
    20  	parent            *GroupRenderer
    21  }
    22  
    23  func NewGroupRenderer(group *controlexecute.ResultGroup, parent *GroupRenderer, maxFailedControls, maxTotalControls int, resultTree *controlexecute.ExecutionTree, width int) *GroupRenderer {
    24  	r := &GroupRenderer{
    25  		group:             group,
    26  		parent:            parent,
    27  		resultTree:        resultTree,
    28  		maxFailedControls: maxFailedControls,
    29  		maxTotalControls:  maxTotalControls,
    30  		width:             width,
    31  	}
    32  	r.lastChild = r.isLastChild(group)
    33  	return r
    34  }
    35  
    36  // are we the last child of our parent?
    37  // this affects the tree rendering
    38  func (r GroupRenderer) isLastChild(group *controlexecute.ResultGroup) bool {
    39  	if group.Parent == nil || group.Parent.GroupItem == nil {
    40  		return true
    41  	}
    42  	siblings := group.Parent.GroupItem.GetChildren()
    43  	// get the name of the last sibling which has controls (or is a control)
    44  	var finalSiblingName string
    45  	for _, s := range siblings {
    46  		if b, ok := s.(*modconfig.Benchmark); ok {
    47  			// find the result group for this benchmark and see if it has controls
    48  			resultGroup := r.resultTree.Root.GetChildGroupByName(b.Name())
    49  			// if the result group has not controls, we will not find it in the result tree
    50  			if resultGroup == nil || resultGroup.ControlRunCount() == 0 {
    51  				continue
    52  			}
    53  		}
    54  		// store the name of this sibling
    55  		finalSiblingName = s.Name()
    56  	}
    57  
    58  	res := group.GroupItem.Name() == finalSiblingName
    59  
    60  	return res
    61  }
    62  
    63  // the indent for blank lines
    64  // same as for (not last) children
    65  func (r GroupRenderer) blankLineIndent() string {
    66  	return r.childIndent()
    67  }
    68  
    69  // the indent for group heading
    70  func (r GroupRenderer) headingIndent() string {
    71  	// if this is the first displayed node, no indent
    72  	if r.parent == nil || r.parent.group.GroupId == controlexecute.RootResultGroupName {
    73  		return ""
    74  	}
    75  	// as our parent for the indent for a group
    76  	i := r.parent.childGroupIndent()
    77  	return i
    78  }
    79  
    80  // the indent for child groups/controls (which are not the final child)
    81  // include the tree '|'
    82  func (r GroupRenderer) childIndent() string {
    83  	return r.parentIndent() + "| "
    84  }
    85  
    86  // the indent for the FINAL child groups/controls
    87  // just a space
    88  func (r GroupRenderer) lastChildIndent() string {
    89  	return r.parentIndent() + "  "
    90  }
    91  
    92  // the indent for child groups - our parent indent with the group expander "+ "
    93  func (r GroupRenderer) childGroupIndent() string {
    94  	return r.parentIndent() + "+ "
    95  }
    96  
    97  // get the indent inherited from our parent
    98  // - this will depend on whether we are our parents last child
    99  func (r GroupRenderer) parentIndent() string {
   100  	if r.parent == nil || r.parent.group.GroupId == controlexecute.RootResultGroupName {
   101  		return ""
   102  	}
   103  	if r.lastChild {
   104  		return r.parent.lastChildIndent()
   105  	}
   106  	return r.parent.childIndent()
   107  }
   108  
   109  func (r GroupRenderer) Render() string {
   110  	if r.width <= 0 {
   111  		// this should never happen, since the minimum width is set by the formatter
   112  		log.Printf("[WARN] group renderer has width of %d\n", r.width)
   113  		return ""
   114  	}
   115  
   116  	if r.group.GroupId == controlexecute.RootResultGroupName {
   117  		return r.renderRootResultGroup()
   118  	}
   119  
   120  	groupHeadingRenderer := NewGroupHeadingRenderer(
   121  		r.group.Title,
   122  		r.group.Summary.Status.FailedCount(),
   123  		r.group.Summary.Status.TotalCount(),
   124  		r.maxFailedControls,
   125  		r.maxTotalControls,
   126  		r.width,
   127  		r.headingIndent())
   128  
   129  	// render this group header
   130  	tableStrings := append([]string{},
   131  		groupHeadingRenderer.Render(),
   132  		// newline after group
   133  		fmt.Sprintf("%s", ControlColors.Indent(r.blankLineIndent())))
   134  
   135  	// now render the group children, in the order they are specified
   136  	childStrings := r.renderChildren()
   137  	tableStrings = append(tableStrings, childStrings...)
   138  	return strings.Join(tableStrings, "\n")
   139  }
   140  
   141  // for root result group, there will either be one or more groups, or one or more control runs
   142  // there will be no order specified so just loop through them
   143  func (r GroupRenderer) renderRootResultGroup() string {
   144  	var resultStrings = make([]string, len(r.group.Groups)+len(r.group.ControlRuns))
   145  	for i, group := range r.group.Groups {
   146  		groupRenderer := NewGroupRenderer(group, &r, r.maxFailedControls, r.maxTotalControls, r.resultTree, r.width)
   147  		resultStrings[i] = groupRenderer.Render()
   148  	}
   149  	for i, run := range r.group.ControlRuns {
   150  		controlRenderer := NewControlRenderer(run, &r)
   151  		resultStrings[i] = controlRenderer.Render()
   152  	}
   153  	return strings.Join(resultStrings, "\n")
   154  }
   155  
   156  // render the children of this group, in the order they are specified in the hcl
   157  func (r GroupRenderer) renderChildren() []string {
   158  	children := r.group.GroupItem.GetChildren()
   159  	var childStrings []string
   160  
   161  	for _, child := range children {
   162  		if control, ok := child.(*modconfig.Control); ok {
   163  			// get Result group with a matching name
   164  			if run := r.group.GetControlRunByName(control.Name()); run != nil {
   165  				controlRenderer := NewControlRenderer(run, &r)
   166  				childStrings = append(childStrings, controlRenderer.Render())
   167  			}
   168  		} else {
   169  			if childGroup := r.group.GetGroupByName(child.Name()); childGroup != nil {
   170  				groupRenderer := NewGroupRenderer(childGroup, &r, r.maxFailedControls, r.maxTotalControls, r.resultTree, r.width)
   171  				childStrings = append(childStrings, groupRenderer.Render())
   172  			}
   173  		}
   174  	}
   175  
   176  	return childStrings
   177  }