github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/workspace/workspace_events.go (about)

     1  package workspace
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"reflect"
     8  	"strings"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/fsnotify/fsnotify"
    13  	"github.com/spf13/viper"
    14  	"github.com/turbot/steampipe/pkg/constants"
    15  	"github.com/turbot/steampipe/pkg/dashboard/dashboardevents"
    16  	"github.com/turbot/steampipe/pkg/db/db_common"
    17  	"github.com/turbot/steampipe/pkg/error_helpers"
    18  	"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
    19  )
    20  
    21  var EventCount int64 = 0
    22  
    23  func (w *Workspace) PublishDashboardEvent(ctx context.Context, e dashboardevents.DashboardEvent) {
    24  	if w.dashboardEventChan != nil {
    25  		var doneChan = make(chan struct{})
    26  		go func() {
    27  			// send an event onto the event bus
    28  			w.dashboardEventChan <- e
    29  			atomic.AddInt64(&EventCount, 1)
    30  			close(doneChan)
    31  		}()
    32  		select {
    33  		case <-doneChan:
    34  		case <-time.After(1 * time.Second):
    35  			log.Printf("[TRACE] timeout sending dashboard event %s, buffered events: %d", reflect.TypeOf(e).String(), EventCount)
    36  		case <-ctx.Done():
    37  			log.Printf("[TRACE] context cancelled sending dashboard event")
    38  		}
    39  	}
    40  }
    41  
    42  // RegisterDashboardEventHandler starts the event handler goroutine if necessary and
    43  // adds the event handler to our list
    44  func (w *Workspace) RegisterDashboardEventHandler(ctx context.Context, handler dashboardevents.DashboardEventHandler) {
    45  	// if no event channel has been created we need to start the event handler goroutine
    46  	if w.dashboardEventChan == nil {
    47  		// create a fairly large channel buffer
    48  		w.dashboardEventChan = make(chan dashboardevents.DashboardEvent, 256)
    49  		go w.handleDashboardEvent(ctx)
    50  	}
    51  	// now add the handler to our list
    52  	w.dashboardEventHandlers = append(w.dashboardEventHandlers, handler)
    53  }
    54  
    55  // UnregisterDashboardEventHandlers clears all event handlers
    56  // used when generating multiple snapshots
    57  func (w *Workspace) UnregisterDashboardEventHandlers() {
    58  	w.dashboardEventHandlers = nil
    59  }
    60  
    61  // this function is run as a goroutine to call registered event handlers for all received events
    62  func (w *Workspace) handleDashboardEvent(ctx context.Context) {
    63  	for {
    64  		e := <-w.dashboardEventChan
    65  		atomic.AddInt64(&EventCount, -1)
    66  		if e == nil {
    67  			log.Printf("[TRACE] handleDashboardEvent nil event received - exiting")
    68  			w.dashboardEventChan = nil
    69  			return
    70  		}
    71  
    72  		for _, handler := range w.dashboardEventHandlers {
    73  			handler(ctx, e)
    74  		}
    75  	}
    76  }
    77  
    78  func (w *Workspace) handleFileWatcherEvent(ctx context.Context, client db_common.Client, ev []fsnotify.Event) {
    79  	log.Printf("[TRACE] handleFileWatcherEvent")
    80  	prevResourceMaps, resourceMaps, errAndWarnings := w.reloadResourceMaps(ctx)
    81  
    82  	if errAndWarnings.GetError() != nil {
    83  		log.Printf("[TRACE] handleFileWatcherEvent reloadResourceMaps returned error - call PublishDashboardEvent")
    84  		// publish error event
    85  		w.PublishDashboardEvent(ctx, &dashboardevents.WorkspaceError{Error: errAndWarnings.GetError()})
    86  		log.Printf("[TRACE] back from PublishDashboardEvent")
    87  		return
    88  	}
    89  	// if resources have changed, update introspection tables
    90  	if !prevResourceMaps.Equals(resourceMaps) {
    91  		// update the client with the new introspection data
    92  		w.onNewIntrospectionData(ctx, client)
    93  
    94  		if w.onFileWatcherEventMessages != nil {
    95  			w.onFileWatcherEventMessages()
    96  		}
    97  	}
    98  	w.raiseDashboardChangedEvents(ctx, resourceMaps, prevResourceMaps)
    99  }
   100  
   101  func (w *Workspace) onNewIntrospectionData(ctx context.Context, client db_common.Client) {
   102  	if viper.GetString(constants.ArgIntrospection) == constants.IntrospectionNone {
   103  		// nothing to do here
   104  		return
   105  	}
   106  	client.ResetPools(ctx)
   107  	res := client.AcquireSession(ctx)
   108  	if res.Session != nil {
   109  		res.Session.Close(error_helpers.IsContextCanceled(ctx))
   110  	}
   111  	if res != nil {
   112  		fmt.Println()
   113  		error_helpers.ShowErrorWithMessage(ctx, res.Error, "error when refreshing session data")
   114  		error_helpers.ShowWarning(strings.Join(res.Warnings, "\n"))
   115  	}
   116  }
   117  
   118  func (w *Workspace) reloadResourceMaps(ctx context.Context) (_ *modconfig.ResourceMaps, _ *modconfig.ResourceMaps, errAndWarnings error_helpers.ErrorAndWarnings) {
   119  	w.loadLock.Lock()
   120  	defer w.loadLock.Unlock()
   121  
   122  	defer func() {
   123  		if errAndWarnings.GetError() != nil {
   124  			// check the existing watcher error - if we are already in an error state, do not show error
   125  			if w.watcherError == nil {
   126  				w.fileWatcherErrorHandler(ctx, error_helpers.PrefixError(errAndWarnings.GetError(), "failed to reload workspace"))
   127  			}
   128  			// now set watcher error to new error
   129  			w.watcherError = errAndWarnings.GetError()
   130  		}
   131  	}()
   132  
   133  	// get the pre-load resource maps
   134  	// NOTE: do not call GetResourceMaps - we DO NOT want to lock loadLock
   135  	prevResourceMaps := w.Mod.ResourceMaps
   136  	// if there is an outstanding watcher error, set prevResourceMaps to empty to force refresh
   137  	if w.watcherError != nil {
   138  		prevResourceMaps = modconfig.NewModResources(w.Mod)
   139  	}
   140  
   141  	// now reload the workspace
   142  	inputVariables, errAndWarnings := w.PopulateVariables(ctx)
   143  	if errAndWarnings.GetError() != nil {
   144  		return nil, nil, errAndWarnings
   145  	}
   146  	errAndWarnings = w.LoadWorkspaceMod(ctx, inputVariables)
   147  	if errAndWarnings.GetError() != nil {
   148  		return nil, nil, errAndWarnings
   149  	}
   150  	// clear watcher error
   151  	w.watcherError = nil
   152  
   153  	// reload the resource maps
   154  	resourceMaps := w.Mod.ResourceMaps
   155  
   156  	return prevResourceMaps, resourceMaps, errAndWarnings
   157  
   158  }
   159  
   160  func (w *Workspace) raiseDashboardChangedEvents(ctx context.Context, resourceMaps, prevResourceMaps *modconfig.ResourceMaps) {
   161  	event := &dashboardevents.DashboardChanged{}
   162  
   163  	// TODO reports can we use a ResourceMaps diff function to do all of this - we are duplicating logic
   164  
   165  	// first detect changes to existing resources and deletions
   166  	for name, prev := range prevResourceMaps.Dashboards {
   167  		if current, ok := resourceMaps.Dashboards[name]; ok {
   168  			diff := prev.Diff(current)
   169  			if diff.HasChanges() {
   170  				event.ChangedDashboards = append(event.ChangedDashboards, diff)
   171  			}
   172  		} else {
   173  			event.DeletedDashboards = append(event.DeletedDashboards, prev)
   174  		}
   175  	}
   176  	for name, prev := range prevResourceMaps.DashboardContainers {
   177  		if current, ok := resourceMaps.DashboardContainers[name]; ok {
   178  			diff := prev.Diff(current)
   179  			if diff.HasChanges() {
   180  				event.ChangedContainers = append(event.ChangedContainers, diff)
   181  			}
   182  		} else {
   183  			event.DeletedContainers = append(event.DeletedContainers, prev)
   184  		}
   185  	}
   186  	for name, prev := range prevResourceMaps.DashboardCards {
   187  		if current, ok := resourceMaps.DashboardCards[name]; ok {
   188  			diff := prev.Diff(current)
   189  			if diff.HasChanges() {
   190  				event.ChangedCards = append(event.ChangedCards, diff)
   191  			}
   192  		} else {
   193  			event.DeletedCards = append(event.DeletedCards, prev)
   194  		}
   195  	}
   196  	for name, prev := range prevResourceMaps.DashboardCharts {
   197  		if current, ok := resourceMaps.DashboardCharts[name]; ok {
   198  			diff := prev.Diff(current)
   199  			if diff.HasChanges() {
   200  				event.ChangedCharts = append(event.ChangedCharts, diff)
   201  			}
   202  		} else {
   203  			event.DeletedCharts = append(event.DeletedCharts, prev)
   204  		}
   205  	}
   206  	for name, prev := range prevResourceMaps.Benchmarks {
   207  		if current, ok := resourceMaps.Benchmarks[name]; ok {
   208  			diff := prev.Diff(current)
   209  			if diff.HasChanges() {
   210  				event.ChangedBenchmarks = append(event.ChangedBenchmarks, diff)
   211  			}
   212  		} else {
   213  			event.DeletedBenchmarks = append(event.DeletedBenchmarks, prev)
   214  		}
   215  	}
   216  	for name, prev := range prevResourceMaps.Controls {
   217  		if current, ok := resourceMaps.Controls[name]; ok {
   218  			diff := prev.Diff(current)
   219  			if diff.HasChanges() {
   220  				event.ChangedControls = append(event.ChangedControls, diff)
   221  			}
   222  		} else {
   223  			event.DeletedControls = append(event.DeletedControls, prev)
   224  		}
   225  	}
   226  	for name, prev := range prevResourceMaps.DashboardFlows {
   227  		if current, ok := resourceMaps.DashboardFlows[name]; ok {
   228  			diff := prev.Diff(current)
   229  			if diff.HasChanges() {
   230  				event.ChangedFlows = append(event.ChangedFlows, diff)
   231  			}
   232  		} else {
   233  			event.DeletedFlows = append(event.DeletedFlows, prev)
   234  		}
   235  	}
   236  	for name, prev := range prevResourceMaps.DashboardGraphs {
   237  		if current, ok := resourceMaps.DashboardGraphs[name]; ok {
   238  			diff := prev.Diff(current)
   239  			if diff.HasChanges() {
   240  				event.ChangedGraphs = append(event.ChangedGraphs, diff)
   241  			}
   242  		} else {
   243  			event.DeletedGraphs = append(event.DeletedGraphs, prev)
   244  		}
   245  	}
   246  	for name, prev := range prevResourceMaps.DashboardHierarchies {
   247  		if current, ok := resourceMaps.DashboardHierarchies[name]; ok {
   248  			diff := prev.Diff(current)
   249  			if diff.HasChanges() {
   250  				event.ChangedHierarchies = append(event.ChangedHierarchies, diff)
   251  			}
   252  		} else {
   253  			event.DeletedHierarchies = append(event.DeletedHierarchies, prev)
   254  		}
   255  	}
   256  	for name, prev := range prevResourceMaps.DashboardImages {
   257  		if current, ok := resourceMaps.DashboardImages[name]; ok {
   258  			diff := prev.Diff(current)
   259  			if diff.HasChanges() {
   260  				event.ChangedImages = append(event.ChangedImages, diff)
   261  			}
   262  		} else {
   263  			event.DeletedImages = append(event.DeletedImages, prev)
   264  		}
   265  	}
   266  	for name, prev := range prevResourceMaps.DashboardNodes {
   267  		if current, ok := resourceMaps.DashboardNodes[name]; ok {
   268  			diff := prev.Diff(current)
   269  			if diff.HasChanges() {
   270  				event.ChangedNodes = append(event.ChangedNodes, diff)
   271  			}
   272  		} else {
   273  			event.DeletedNodes = append(event.DeletedNodes, prev)
   274  		}
   275  	}
   276  	for name, prev := range prevResourceMaps.DashboardEdges {
   277  		if current, ok := resourceMaps.DashboardEdges[name]; ok {
   278  			diff := prev.Diff(current)
   279  			if diff.HasChanges() {
   280  				event.ChangedEdges = append(event.ChangedEdges, diff)
   281  			}
   282  		} else {
   283  			event.DeletedEdges = append(event.DeletedEdges, prev)
   284  		}
   285  	}
   286  	for name, prev := range prevResourceMaps.GlobalDashboardInputs {
   287  		if current, ok := resourceMaps.GlobalDashboardInputs[name]; ok {
   288  			diff := prev.Diff(current)
   289  			if diff.HasChanges() {
   290  				event.ChangedInputs = append(event.ChangedInputs, diff)
   291  			}
   292  		} else {
   293  			event.DeletedInputs = append(event.DeletedInputs, prev)
   294  		}
   295  	}
   296  	for name, prevInputsForDashboard := range prevResourceMaps.DashboardInputs {
   297  		if currentInputsForDashboard, ok := resourceMaps.DashboardInputs[name]; ok {
   298  			for name, prev := range prevInputsForDashboard {
   299  				if current, ok := currentInputsForDashboard[name]; ok {
   300  					diff := prev.Diff(current)
   301  					if diff.HasChanges() {
   302  						event.ChangedInputs = append(event.ChangedInputs, diff)
   303  					}
   304  				} else {
   305  					event.DeletedInputs = append(event.DeletedInputs, prev)
   306  				}
   307  			}
   308  		} else {
   309  			for _, prev := range prevInputsForDashboard {
   310  				event.DeletedInputs = append(event.DeletedInputs, prev)
   311  			}
   312  		}
   313  	}
   314  	for name, prev := range prevResourceMaps.DashboardTables {
   315  		if current, ok := resourceMaps.DashboardTables[name]; ok {
   316  			diff := prev.Diff(current)
   317  			if diff.HasChanges() {
   318  				event.ChangedTables = append(event.ChangedTables, diff)
   319  			}
   320  		} else {
   321  			event.DeletedTables = append(event.DeletedTables, prev)
   322  		}
   323  	}
   324  	for name, prev := range prevResourceMaps.DashboardCategories {
   325  		if current, ok := resourceMaps.DashboardCategories[name]; ok {
   326  			diff := prev.Diff(current)
   327  			if diff.HasChanges() {
   328  				event.ChangedCategories = append(event.ChangedCategories, diff)
   329  			}
   330  		} else {
   331  			event.DeletedCategories = append(event.DeletedCategories, prev)
   332  		}
   333  	}
   334  	for name, prev := range prevResourceMaps.DashboardTexts {
   335  		if current, ok := resourceMaps.DashboardTexts[name]; ok {
   336  			diff := prev.Diff(current)
   337  			if diff.HasChanges() {
   338  				event.ChangedTexts = append(event.ChangedTexts, diff)
   339  			}
   340  		} else {
   341  			event.DeletedTexts = append(event.DeletedTexts, prev)
   342  		}
   343  	}
   344  
   345  	// now detect new resources
   346  	for name, p := range resourceMaps.Dashboards {
   347  		if _, ok := prevResourceMaps.Dashboards[name]; !ok {
   348  			event.NewDashboards = append(event.NewDashboards, p)
   349  		}
   350  	}
   351  	for name, p := range resourceMaps.DashboardContainers {
   352  		if _, ok := prevResourceMaps.DashboardContainers[name]; !ok {
   353  			event.NewContainers = append(event.NewContainers, p)
   354  		}
   355  	}
   356  	for name, p := range resourceMaps.DashboardCards {
   357  		if _, ok := prevResourceMaps.DashboardCards[name]; !ok {
   358  			event.NewCards = append(event.NewCards, p)
   359  		}
   360  	}
   361  	for name, p := range resourceMaps.DashboardCategories {
   362  		if _, ok := prevResourceMaps.DashboardCategories[name]; !ok {
   363  			event.NewCategories = append(event.NewCategories, p)
   364  		}
   365  	}
   366  	for name, p := range resourceMaps.DashboardCharts {
   367  		if _, ok := prevResourceMaps.DashboardCharts[name]; !ok {
   368  			event.NewCharts = append(event.NewCharts, p)
   369  		}
   370  	}
   371  	for name, p := range resourceMaps.Benchmarks {
   372  		if _, ok := prevResourceMaps.Benchmarks[name]; !ok {
   373  			event.NewBenchmarks = append(event.NewBenchmarks, p)
   374  		}
   375  	}
   376  	for name, p := range resourceMaps.Controls {
   377  		if _, ok := prevResourceMaps.Controls[name]; !ok {
   378  			event.NewControls = append(event.NewControls, p)
   379  		}
   380  	}
   381  	for name, p := range resourceMaps.DashboardFlows {
   382  		if _, ok := prevResourceMaps.DashboardFlows[name]; !ok {
   383  			event.NewFlows = append(event.NewFlows, p)
   384  		}
   385  	}
   386  	for name, p := range resourceMaps.DashboardGraphs {
   387  		if _, ok := prevResourceMaps.DashboardGraphs[name]; !ok {
   388  			event.NewGraphs = append(event.NewGraphs, p)
   389  		}
   390  	}
   391  	for name, p := range resourceMaps.DashboardHierarchies {
   392  		if _, ok := prevResourceMaps.DashboardHierarchies[name]; !ok {
   393  			event.NewHierarchies = append(event.NewHierarchies, p)
   394  		}
   395  	}
   396  	for name, p := range resourceMaps.DashboardImages {
   397  		if _, ok := prevResourceMaps.DashboardImages[name]; !ok {
   398  			event.NewImages = append(event.NewImages, p)
   399  		}
   400  	}
   401  	for name, p := range resourceMaps.DashboardNodes {
   402  		if _, ok := prevResourceMaps.DashboardNodes[name]; !ok {
   403  			event.NewNodes = append(event.NewNodes, p)
   404  		}
   405  	}
   406  	for name, p := range resourceMaps.DashboardEdges {
   407  		if _, ok := prevResourceMaps.DashboardEdges[name]; !ok {
   408  			event.NewEdges = append(event.NewEdges, p)
   409  		}
   410  	}
   411  	for name, p := range resourceMaps.GlobalDashboardInputs {
   412  		if _, ok := prevResourceMaps.GlobalDashboardInputs[name]; !ok {
   413  			event.NewInputs = append(event.NewInputs, p)
   414  		}
   415  	}
   416  
   417  	for name, currentInputsForDashboard := range resourceMaps.DashboardInputs {
   418  		if prevInputsForDashboard, ok := prevResourceMaps.DashboardInputs[name]; ok {
   419  			for name, current := range currentInputsForDashboard {
   420  				if _, ok := prevInputsForDashboard[name]; !ok {
   421  					event.NewInputs = append(event.NewInputs, current)
   422  				}
   423  			}
   424  		} else {
   425  			// all new
   426  			for _, current := range currentInputsForDashboard {
   427  				event.NewInputs = append(event.NewInputs, current)
   428  			}
   429  		}
   430  	}
   431  
   432  	for name, p := range resourceMaps.DashboardTables {
   433  		if _, ok := prevResourceMaps.DashboardTables[name]; !ok {
   434  			event.NewTables = append(event.NewTables, p)
   435  		}
   436  	}
   437  	for name, p := range resourceMaps.DashboardTexts {
   438  		if _, ok := prevResourceMaps.DashboardTexts[name]; !ok {
   439  			event.NewTexts = append(event.NewTexts, p)
   440  		}
   441  	}
   442  
   443  	if event.HasChanges() {
   444  		// for every changed resource, set parents as changed, up the tree
   445  		f := func(item modconfig.ModTreeItem) (bool, error) {
   446  			event.SetParentsChanged(item, prevResourceMaps)
   447  			return true, nil
   448  		}
   449  		event.WalkChangedResources(f)
   450  		w.PublishDashboardEvent(ctx, event)
   451  	}
   452  }