github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/dashboard/dashboardexecute/leaf_run.go (about)

     1  package dashboardexecute
     2  
     3  import (
     4  	"context"
     5  	"log"
     6  
     7  	"github.com/turbot/steampipe/pkg/dashboard/dashboardtypes"
     8  	"github.com/turbot/steampipe/pkg/error_helpers"
     9  	"github.com/turbot/steampipe/pkg/query/queryresult"
    10  	"github.com/turbot/steampipe/pkg/statushooks"
    11  	"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
    12  	"golang.org/x/exp/maps"
    13  )
    14  
    15  // LeafRun is a struct representing the execution of a leaf dashboard node
    16  type LeafRun struct {
    17  	// all RuntimeDependencySubscribers are also publishers as they have args/params
    18  	RuntimeDependencySubscriberImpl
    19  	Resource modconfig.DashboardLeafNode `json:"properties,omitempty"`
    20  
    21  	Data         *dashboardtypes.LeafData  `json:"data,omitempty"`
    22  	TimingResult *queryresult.TimingResult `json:"-"`
    23  	// function called when the run is complete
    24  	// this property populated for 'with' runs
    25  	onComplete func()
    26  }
    27  
    28  func (r *LeafRun) AsTreeNode() *dashboardtypes.SnapshotTreeNode {
    29  	return &dashboardtypes.SnapshotTreeNode{
    30  		Name:     r.Name,
    31  		NodeType: r.NodeType,
    32  	}
    33  }
    34  
    35  func NewLeafRun(resource modconfig.DashboardLeafNode, parent dashboardtypes.DashboardParent, executionTree *DashboardExecutionTree, opts ...LeafRunOption) (*LeafRun, error) {
    36  	r := &LeafRun{
    37  		Resource: resource,
    38  	}
    39  
    40  	// create RuntimeDependencySubscriberImpl- this handles 'with' run creation and resolving runtime dependency resolution
    41  	// (NOTE: we have to do this after creating run as we need to pass a ref to the run)
    42  	r.RuntimeDependencySubscriberImpl = *NewRuntimeDependencySubscriber(resource, parent, r, executionTree)
    43  
    44  	// apply options AFTER calling NewRuntimeDependencySubscriber
    45  	for _, opt := range opts {
    46  		opt(r)
    47  	}
    48  
    49  	err := r.initRuntimeDependencies(executionTree)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	r.NodeType = resource.BlockType()
    55  
    56  	// if the node has no runtime dependencies, resolve the sql
    57  	if !r.hasRuntimeDependencies() {
    58  		if err := r.resolveSQLAndArgs(); err != nil {
    59  			return nil, err
    60  		}
    61  	}
    62  	// add r into execution tree
    63  	executionTree.runs[r.Name] = r
    64  
    65  	// if we have children (nodes/edges), create runs for them
    66  	err = r.createChildRuns(executionTree)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	// create buffered channel for children to report their completion
    72  	r.createChildCompleteChan()
    73  
    74  	// populate the names of any withs we depend on
    75  	r.setRuntimeDependencies()
    76  
    77  	return r, nil
    78  }
    79  
    80  func (r *LeafRun) createChildRuns(executionTree *DashboardExecutionTree) error {
    81  	children := r.resource.GetChildren()
    82  	if len(children) == 0 {
    83  		return nil
    84  	}
    85  
    86  	r.children = make([]dashboardtypes.DashboardTreeRun, len(children))
    87  	var errors []error
    88  
    89  	for i, c := range children {
    90  		var opts []LeafRunOption
    91  		childRun, err := NewLeafRun(c.(modconfig.DashboardLeafNode), r, executionTree, opts...)
    92  		if err != nil {
    93  			errors = append(errors, err)
    94  			continue
    95  		}
    96  
    97  		r.children[i] = childRun
    98  	}
    99  	return error_helpers.CombineErrors(errors...)
   100  }
   101  
   102  // Execute implements DashboardTreeRun
   103  func (r *LeafRun) Execute(ctx context.Context) {
   104  	defer func() {
   105  		// call our oncomplete is we have one
   106  		// (this is used to collect 'with' data and propagate errors)
   107  		if r.onComplete != nil {
   108  			r.onComplete()
   109  		}
   110  	}()
   111  
   112  	// if there is nothing to do, return
   113  	if r.Status.IsFinished() {
   114  		return
   115  	}
   116  
   117  	log.Printf("[TRACE] LeafRun '%s' Execute()", r.resource.Name())
   118  
   119  	// to get here, we must be a query provider
   120  
   121  	// if we have children and with runs, start them asyncronously (they may block waiting for our runtime dependencies)
   122  	r.executeChildrenAsync(ctx)
   123  
   124  	// start a goroutine to wait for children to complete
   125  	doneChan := r.waitForChildrenAsync(ctx)
   126  
   127  	if err := r.evaluateRuntimeDependencies(ctx); err != nil {
   128  		r.SetError(ctx, err)
   129  		return
   130  	}
   131  
   132  	// set status to running (this sends update event)
   133  	// (if we have blocked children, this will be changed to blocked)
   134  	r.setRunning(ctx)
   135  
   136  	// if we have sql to execute, do it now
   137  	// (if we are only performing a base execution, do not run the query)
   138  	if r.executeSQL != "" {
   139  		if err := r.executeQuery(ctx); err != nil {
   140  			r.SetError(ctx, err)
   141  			return
   142  		}
   143  	}
   144  
   145  	// wait for all children and withs
   146  	err := <-doneChan
   147  	if err == nil {
   148  		log.Printf("[TRACE] %s children complete", r.resource.Name())
   149  
   150  		// aggregate our child data
   151  		r.combineChildData()
   152  		// set complete status on dashboard
   153  		r.SetComplete(ctx)
   154  	} else {
   155  		log.Printf("[TRACE] %s children complete with error: %s", r.resource.Name(), err.Error())
   156  		r.SetError(ctx, err)
   157  	}
   158  }
   159  
   160  // SetError implements DashboardTreeRun (override to set snapshothook status)
   161  func (r *LeafRun) SetError(ctx context.Context, err error) {
   162  	// increment error count for snapshot hook
   163  	statushooks.SnapshotError(ctx)
   164  	r.DashboardTreeRunImpl.SetError(ctx, err)
   165  }
   166  
   167  // SetComplete implements DashboardTreeRun (override to set snapshothook status
   168  func (r *LeafRun) SetComplete(ctx context.Context) {
   169  	// call snapshot hooks with progress
   170  	statushooks.UpdateSnapshotProgress(ctx, 1)
   171  
   172  	r.DashboardTreeRunImpl.SetComplete(ctx)
   173  }
   174  
   175  // IsSnapshotPanel implements SnapshotPanel
   176  func (*LeafRun) IsSnapshotPanel() {}
   177  
   178  // if this leaf run has a query or sql, execute it now
   179  func (r *LeafRun) executeQuery(ctx context.Context) error {
   180  	log.Printf("[TRACE] LeafRun '%s' SQL resolved, executing", r.resource.Name())
   181  
   182  	queryResult, err := r.executionTree.client.ExecuteSync(ctx, r.executeSQL, r.Args...)
   183  	if err != nil {
   184  		log.Printf("[TRACE] LeafRun '%s' query failed: %s", r.resource.Name(), err.Error())
   185  		return err
   186  
   187  	}
   188  	log.Printf("[TRACE] LeafRun '%s' complete", r.resource.Name())
   189  
   190  	r.Data = dashboardtypes.NewLeafData(queryResult)
   191  	r.TimingResult = queryResult.TimingResult
   192  	return nil
   193  }
   194  
   195  func (r *LeafRun) combineChildData() {
   196  	// we either have children OR a query
   197  	// if there are no children, do nothing
   198  	if len(r.children) == 0 {
   199  		return
   200  	}
   201  	// create empty data to populate
   202  	r.Data = &dashboardtypes.LeafData{}
   203  	// build map of columns for the schema
   204  	schemaMap := make(map[string]*queryresult.ColumnDef)
   205  	for _, c := range r.children {
   206  		childLeafRun := c.(*LeafRun)
   207  		data := childLeafRun.Data
   208  		// if there is no data or this is a 'with', skip
   209  		if data == nil || childLeafRun.resource.BlockType() == modconfig.BlockTypeWith {
   210  			continue
   211  		}
   212  		for _, s := range data.Columns {
   213  			if _, ok := schemaMap[s.Name]; !ok {
   214  				schemaMap[s.Name] = s
   215  			}
   216  		}
   217  		r.Data.Rows = append(r.Data.Rows, data.Rows...)
   218  	}
   219  	r.Data.Columns = maps.Values(schemaMap)
   220  }