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 }