diff --git a/dashboard/dashboardevents/execution_started.go b/dashboard/dashboardevents/execution_started.go index 02e5df5d2..40b9c587b 100644 --- a/dashboard/dashboardevents/execution_started.go +++ b/dashboard/dashboardevents/execution_started.go @@ -3,8 +3,8 @@ package dashboardevents import "github.com/turbot/steampipe/dashboard/dashboardinterfaces" type ExecutionStarted struct { - DashboardNode dashboardinterfaces.DashboardNodeRun `json:"dashboard"` - Session string + Dashboard dashboardinterfaces.DashboardNodeRun `json:"dashboard"` + Session string } // IsDashboardEvent implements DashboardEvent interface diff --git a/dashboard/dashboardevents/leaf_node_complete.go b/dashboard/dashboardevents/leaf_node_complete.go index 57405ae8e..98700ba2f 100644 --- a/dashboard/dashboardevents/leaf_node_complete.go +++ b/dashboard/dashboardevents/leaf_node_complete.go @@ -3,8 +3,8 @@ package dashboardevents import "github.com/turbot/steampipe/dashboard/dashboardinterfaces" type LeafNodeComplete struct { - Node dashboardinterfaces.DashboardNodeRun - Session string + LeafNode dashboardinterfaces.DashboardNodeRun + Session string } // IsDashboardEvent implements DashboardEvent interface diff --git a/dashboard/dashboardevents/leaf_node_error.go b/dashboard/dashboardevents/leaf_node_error.go index c217cb962..6e0489f2b 100644 --- a/dashboard/dashboardevents/leaf_node_error.go +++ b/dashboard/dashboardevents/leaf_node_error.go @@ -3,8 +3,9 @@ package dashboardevents import "github.com/turbot/steampipe/dashboard/dashboardinterfaces" type LeafNodeError struct { - Node dashboardinterfaces.DashboardNodeRun - Session string + LeafNode dashboardinterfaces.DashboardNodeRun + Session string + Error error } // IsDashboardEvent implements DashboardEvent interface diff --git a/dashboard/dashboardevents/leaf_node_progress.go b/dashboard/dashboardevents/leaf_node_progress.go index 9d74a738b..c685748f0 100644 --- a/dashboard/dashboardevents/leaf_node_progress.go +++ b/dashboard/dashboardevents/leaf_node_progress.go @@ -3,8 +3,8 @@ package dashboardevents import "github.com/turbot/steampipe/dashboard/dashboardinterfaces" type LeafNodeProgress struct { - Node dashboardinterfaces.DashboardNodeRun - Session string + LeafNode dashboardinterfaces.DashboardNodeRun + Session string } // IsDashboardEvent implements DashboardEvent interface diff --git a/dashboard/dashboardexecute/check_run.go b/dashboard/dashboardexecute/check_run.go index 3d1726af1..dffed6d30 100644 --- a/dashboard/dashboardexecute/check_run.go +++ b/dashboard/dashboardexecute/check_run.go @@ -22,21 +22,22 @@ type CheckRun struct { ControlExecutionTree *controlexecute.ExecutionTree `json:"execution_tree"` DashboardName string `json:"dashboard"` DashboardNode modconfig.DashboardLeafNode `json:"-"` - Path []string `json:"-"` parent dashboardinterfaces.DashboardNodeParent runStatus dashboardinterfaces.DashboardRunStatus executionTree *DashboardExecutionTree } func NewCheckRun(resource modconfig.DashboardLeafNode, parent dashboardinterfaces.DashboardNodeParent, executionTree *DashboardExecutionTree) (*CheckRun, error) { - // ensure the tree node name is unique - name := executionTree.GetUniqueName(resource.Name()) + + // NOTE: for now we MUST declare container/dashboard children inline - therefore we cannot share children between runs in the tree + // (if we supported the children property then we could reuse resources) + // so FOR NOW it is safe to use the node name directly as the run name + name := resource.Name() r := &CheckRun{ Name: name, Title: resource.GetTitle(), Width: resource.GetWidth(), - Path: resource.GetPaths()[0], DashboardNode: resource, DashboardName: executionTree.dashboardName, executionTree: executionTree, @@ -88,11 +89,6 @@ func (r *CheckRun) GetName() string { return r.Name } -// GetPath implements DashboardNodeRun -func (r *CheckRun) GetPath() modconfig.NodePath { - return r.Path -} - // GetRunStatus implements DashboardNodeRun func (r *CheckRun) GetRunStatus() dashboardinterfaces.DashboardRunStatus { return r.runStatus @@ -104,8 +100,9 @@ func (r *CheckRun) SetError(err error) { r.runStatus = dashboardinterfaces.DashboardRunError // raise dashboard error event r.executionTree.workspace.PublishDashboardEvent(&dashboardevents.LeafNodeError{ - Node: r, - Session: r.executionTree.sessionId, + LeafNode: r, + Session: r.executionTree.sessionId, + Error: err, }) // tell parent we are done r.parent.ChildCompleteChan() <- r @@ -117,8 +114,8 @@ func (r *CheckRun) SetComplete() { r.runStatus = dashboardinterfaces.DashboardRunComplete // raise counter complete event r.executionTree.workspace.PublishDashboardEvent(&dashboardevents.LeafNodeComplete{ - Node: r, - Session: r.executionTree.sessionId, + LeafNode: r, + Session: r.executionTree.sessionId, }) // tell parent we are done r.parent.ChildCompleteChan() <- r diff --git a/dashboard/dashboardexecute/container_run.go b/dashboard/dashboardexecute/container_run.go index 258a005a8..cf47d1e91 100644 --- a/dashboard/dashboardexecute/container_run.go +++ b/dashboard/dashboardexecute/container_run.go @@ -24,7 +24,6 @@ type DashboardContainerRun struct { NodeType string `json:"node_type"` Status dashboardinterfaces.DashboardRunStatus `json:"status"` DashboardName string `json:"report"` - Path []string `json:"-"` dashboardNode *modconfig.DashboardContainer parent dashboardinterfaces.DashboardNodeParent executionTree *DashboardExecutionTree @@ -34,13 +33,14 @@ type DashboardContainerRun struct { func NewDashboardContainerRun(container *modconfig.DashboardContainer, parent dashboardinterfaces.DashboardNodeParent, executionTree *DashboardExecutionTree) (*DashboardContainerRun, error) { children := container.GetChildren() - // ensure the tree node name is unique - name := executionTree.GetUniqueName(container.Name()) + // NOTE: for now we MUST declare children inline - therefore we cannot share children between runs in the tree + // (if we supported the children property then we could reuse resources) + // so FOR NOW it is safe to use the container name directly as the run name + name := container.Name() r := &DashboardContainerRun{ Name: name, NodeType: modconfig.BlockTypeContainer, - Path: container.Paths[0], DashboardName: executionTree.dashboardName, executionTree: executionTree, parent: parent, @@ -153,11 +153,6 @@ func (r *DashboardContainerRun) GetName() string { return r.Name } -// GetPath implements DashboardNodeRun -func (r *DashboardContainerRun) GetPath() modconfig.NodePath { - return r.Path -} - // GetRunStatus implements DashboardNodeRun func (r *DashboardContainerRun) GetRunStatus() dashboardinterfaces.DashboardRunStatus { return r.Status diff --git a/dashboard/dashboardexecute/control_event_hooks.go b/dashboard/dashboardexecute/control_event_hooks.go index da3dd7cff..7f4b568f8 100644 --- a/dashboard/dashboardexecute/control_event_hooks.go +++ b/dashboard/dashboardexecute/control_event_hooks.go @@ -23,7 +23,7 @@ func (c *ControlEventHooks) OnStart(ctx context.Context, _ *controlhooks.Control } func (c *ControlEventHooks) OnControlEvent(ctx context.Context, _ *controlhooks.ControlProgress) { - event := &dashboardevents.LeafNodeProgress{Node: c.CheckRun} + event := &dashboardevents.LeafNodeProgress{LeafNode: c.CheckRun} c.CheckRun.executionTree.workspace.PublishDashboardEvent(event) } diff --git a/dashboard/dashboardexecute/dashboard_execution_tree.go b/dashboard/dashboardexecute/dashboard_execution_tree.go index e3521e894..58b11a9fb 100644 --- a/dashboard/dashboardexecute/dashboard_execution_tree.go +++ b/dashboard/dashboardexecute/dashboard_execution_tree.go @@ -15,8 +15,6 @@ import ( // DashboardExecutionTree is a structure representing the control result hierarchy type DashboardExecutionTree struct { - modconfig.UniqueNameProviderBase - Root *DashboardRun dashboardName string @@ -80,8 +78,8 @@ func (e *DashboardExecutionTree) Execute(ctx context.Context) error { e.cancel = cancel workspace := e.workspace workspace.PublishDashboardEvent(&dashboardevents.ExecutionStarted{ - DashboardNode: e.Root, - Session: e.sessionId, + Dashboard: e.Root, + Session: e.sessionId, }) defer workspace.PublishDashboardEvent(&dashboardevents.ExecutionComplete{ Dashboard: e.Root, diff --git a/dashboard/dashboardexecute/dashboard_run.go b/dashboard/dashboardexecute/dashboard_run.go index 093eb3f66..66dc61666 100644 --- a/dashboard/dashboardexecute/dashboard_run.go +++ b/dashboard/dashboardexecute/dashboard_run.go @@ -27,7 +27,6 @@ type DashboardRun struct { NodeType string `json:"node_type"` Status dashboardinterfaces.DashboardRunStatus `json:"status"` DashboardName string `json:"dashboard"` - Path []string `json:"-"` dashboardNode *modconfig.Dashboard parent dashboardinterfaces.DashboardNodeParent executionTree *DashboardExecutionTree @@ -37,13 +36,14 @@ type DashboardRun struct { func NewDashboardRun(dashboard *modconfig.Dashboard, parent dashboardinterfaces.DashboardNodeParent, executionTree *DashboardExecutionTree) (*DashboardRun, error) { children := dashboard.GetChildren() - // ensure the tree node name is unique - name := executionTree.GetUniqueName(dashboard.Name()) + // NOTE: for now we MUST declare container/dashboard children inline - therefore we cannot share children between runs in the tree + // (if we supported the children property then we could reuse resources) + // so FOR NOW it is safe to use the dashboard name directly as the run name + name := dashboard.Name() r := &DashboardRun{ Name: name, NodeType: modconfig.BlockTypeDashboard, - Path: dashboard.Paths[0], DashboardName: executionTree.dashboardName, executionTree: executionTree, parent: parent, @@ -162,11 +162,6 @@ func (r *DashboardRun) GetName() string { return r.Name } -// GetPath implements DashboardNodeRun -func (r *DashboardRun) GetPath() modconfig.NodePath { - return r.Path -} - // GetRunStatus implements DashboardNodeRun func (r *DashboardRun) GetRunStatus() dashboardinterfaces.DashboardRunStatus { return r.Status diff --git a/dashboard/dashboardexecute/leaf_run.go b/dashboard/dashboardexecute/leaf_run.go index dd86df230..d537909ed 100644 --- a/dashboard/dashboardexecute/leaf_run.go +++ b/dashboard/dashboardexecute/leaf_run.go @@ -3,6 +3,7 @@ package dashboardexecute import ( "context" "fmt" + "log" "github.com/turbot/steampipe/dashboard/dashboardevents" "github.com/turbot/steampipe/dashboard/dashboardinterfaces" @@ -21,7 +22,6 @@ type LeafRun struct { DashboardNode modconfig.DashboardLeafNode `json:"properties"` NodeType string `json:"node_type"` DashboardName string `json:"dashboard"` - Path []string `json:"-"` parent dashboardinterfaces.DashboardNodeParent runStatus dashboardinterfaces.DashboardRunStatus executionTree *DashboardExecutionTree @@ -29,14 +29,16 @@ type LeafRun struct { } func NewLeafRun(resource modconfig.DashboardLeafNode, parent dashboardinterfaces.DashboardNodeParent, executionTree *DashboardExecutionTree) (*LeafRun, error) { - // ensure the tree node name is unique - name := executionTree.GetUniqueName(resource.Name()) + + // NOTE: for now we MUST declare container/dashboard children inline - therefore we cannot share children between runs in the tree + // (if we supported the children property then we could reuse resources) + // so FOR NOW it is safe to use the node name directly as the run name + name := resource.Name() r := &LeafRun{ Name: name, Title: resource.GetTitle(), Width: resource.GetWidth(), - Path: resource.GetPaths()[0], DashboardNode: resource, DashboardName: executionTree.dashboardName, executionTree: executionTree, @@ -65,7 +67,6 @@ func NewLeafRun(resource modconfig.DashboardLeafNode, parent dashboardinterfaces for name, dep := range runtimeDependencies { r.runtimeDependencies[name] = NewResolvedRuntimeDependency(dep, executionTree) } - } // add r into execution tree @@ -80,12 +81,14 @@ func (r *LeafRun) Execute(ctx context.Context) error { return nil } + log.Printf("[TRACE] LeafRun '%s' Execute()", r.DashboardNode.Name()) + // if there are any unresolved runtime dependencies, wait for them if err := r.waitForRuntimeDependencies(ctx); err != nil { return err } - // ok now we have runtime depdencies, we can resolve the query + // ok now we have runtime dependencies, we can resolve the query queryProvider := r.DashboardNode.(modconfig.QueryProvider) sql, err := r.executionTree.workspace.ResolveQueryFromQueryProvider(queryProvider, nil) if err != nil { @@ -93,13 +96,18 @@ func (r *LeafRun) Execute(ctx context.Context) error { } r.SQL = sql + log.Printf("[TRACE] LeafRun '%s' SQL resolved, executing", r.DashboardNode.Name()) + queryResult, err := r.executionTree.client.ExecuteSync(ctx, r.SQL) if err != nil { + log.Printf("[TRACE] LeafRun '%s' query failed: %s", r.DashboardNode.Name(), err.Error()) // set the error status on the counter - this will raise counter error event r.SetError(err) return err } + log.Printf("[TRACE] LeafRun '%s' complete", r.DashboardNode.Name()) + r.Data = NewLeafData(queryResult) // set complete status on counter - this will raise counter complete event r.SetComplete() @@ -111,11 +119,6 @@ func (r *LeafRun) GetName() string { return r.Name } -// GetPath implements DashboardNodeRun -func (r *LeafRun) GetPath() modconfig.NodePath { - return r.Path -} - // GetRunStatus implements DashboardNodeRun func (r *LeafRun) GetRunStatus() dashboardinterfaces.DashboardRunStatus { return r.runStatus @@ -127,8 +130,8 @@ func (r *LeafRun) SetError(err error) { r.runStatus = dashboardinterfaces.DashboardRunError // raise counter error event r.executionTree.workspace.PublishDashboardEvent(&dashboardevents.LeafNodeError{ - Node: r, - Session: r.executionTree.sessionId, + LeafNode: r, + Session: r.executionTree.sessionId, }) // tell parent we are done r.parent.ChildCompleteChan() <- r @@ -140,8 +143,8 @@ func (r *LeafRun) SetComplete() { r.runStatus = dashboardinterfaces.DashboardRunComplete // raise counter complete event r.executionTree.workspace.PublishDashboardEvent(&dashboardevents.LeafNodeComplete{ - Node: r, - Session: r.executionTree.sessionId, + LeafNode: r, + Session: r.executionTree.sessionId, }) // tell parent we are done r.parent.ChildCompleteChan() <- r @@ -158,19 +161,27 @@ func (r *LeafRun) ChildrenComplete() bool { } func (r *LeafRun) waitForRuntimeDependencies(ctx context.Context) error { + log.Printf("[TRACE] LeafRun '%s' waitForRuntimeDependencies", r.DashboardNode.Name()) for _, resolvedDependency := range r.runtimeDependencies { // check with the top level dashboard whether the dependency is available if !resolvedDependency.Resolve() { + log.Printf("[TRACE] waitForRuntimeDependency %s", resolvedDependency.dependency.String()) if err := r.executionTree.waitForRuntimeDependency(ctx, resolvedDependency.dependency); err != nil { return err } } + + log.Printf("[TRACE] dependency %s should be available", resolvedDependency.dependency.String()) // now again resolve the dependency value - this sets the arg to have the runtime dependency value if !resolvedDependency.Resolve() { + log.Printf("[TRACE] dependency %s not resolved after waitForRuntimeDependency returned", resolvedDependency.dependency.String()) // should now be resolved` - return fmt.Errorf("dependency not resolved after waitForRuntimeDependency returned") + return fmt.Errorf("dependency %s not resolved after waitForRuntimeDependency returned", resolvedDependency.dependency.String()) } } + if len(r.runtimeDependencies) > 0 { + log.Printf("[TRACE] LeafRun '%s' all runtime dependencies ready", r.DashboardNode.Name()) + } return nil } diff --git a/dashboard/dashboardinterfaces/run_interface.go b/dashboard/dashboardinterfaces/run_interface.go index 01ac4f907..c5e4bbcd2 100644 --- a/dashboard/dashboardinterfaces/run_interface.go +++ b/dashboard/dashboardinterfaces/run_interface.go @@ -2,8 +2,6 @@ package dashboardinterfaces import ( "context" - - "github.com/turbot/steampipe/steampipeconfig/modconfig" ) type DashboardRunStatus string @@ -19,7 +17,6 @@ const ( type DashboardNodeRun interface { Execute(ctx context.Context) error GetName() string - GetPath() modconfig.NodePath GetRunStatus() DashboardRunStatus SetError(err error) SetComplete() diff --git a/dashboard/dashboardserver/server.go b/dashboard/dashboardserver/server.go index 6f6a11b14..abc648cfb 100644 --- a/dashboard/dashboardserver/server.go +++ b/dashboard/dashboardserver/server.go @@ -157,7 +157,7 @@ func buildWorkspaceErrorPayload(e *dashboardevents.WorkspaceError) ([]byte, erro func buildLeafNodeProgressPayload(event *dashboardevents.LeafNodeProgress) ([]byte, error) { payload := ExecutionPayload{ Action: "leaf_node_progress", - DashboardNode: event.Node, + DashboardNode: event.LeafNode, } return json.Marshal(payload) } @@ -165,7 +165,7 @@ func buildLeafNodeProgressPayload(event *dashboardevents.LeafNodeProgress) ([]by func buildLeafNodeCompletePayload(event *dashboardevents.LeafNodeComplete) ([]byte, error) { payload := ExecutionPayload{ Action: "leaf_node_complete", - DashboardNode: event.Node, + DashboardNode: event.LeafNode, } return json.Marshal(payload) } @@ -173,11 +173,8 @@ func buildLeafNodeCompletePayload(event *dashboardevents.LeafNodeComplete) ([]by func buildExecutionStartedPayload(event *dashboardevents.ExecutionStarted) ([]byte, error) { payload := ExecutionPayload{ Action: "execution_started", - DashboardNode: event.DashboardNode, + DashboardNode: event.Dashboard, } - a, _ := json.MarshalIndent(payload, "", " ") - b := string(a) - fmt.Println(b) return json.Marshal(payload) } @@ -252,7 +249,7 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { switch e := event.(type) { case *dashboardevents.WorkspaceError: - log.Println("[TRACE] Got workspace error event", *e) + log.Printf("[TRACE] WorkspaceError event: %s", e.Error) payload, payloadError = buildWorkspaceErrorPayload(e) if payloadError != nil { return @@ -261,7 +258,7 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { outputError(s.context, e.Error) case *dashboardevents.ExecutionStarted: - log.Println("[TRACE] Got execution started event", *e) + log.Printf("[TRACE] ExecutionStarted event session %s, dashboard %s", e.Session, e.Dashboard.GetName()) payload, payloadError = buildExecutionStartedPayload(e) if payloadError != nil { return @@ -269,13 +266,13 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { s.mutex.Lock() s.writePayloadToSession(e.Session, payload) s.mutex.Unlock() - outputWait(s.context, fmt.Sprintf("Dashboard execution started: %s", e.DashboardNode.GetName())) + outputWait(s.context, fmt.Sprintf("Dashboard execution started: %s", e.Dashboard.GetName())) case *dashboardevents.LeafNodeError: - log.Println("[TRACE] Got leaf node error event", *e) + log.Printf("[TRACE] LeafNodeError event session %s, node %s, error %s", e.Session, e.LeafNode.GetName(), e.Error) case *dashboardevents.LeafNodeProgress: - log.Println("[TRACE] Got leaf node complete event", *e) + log.Printf("[TRACE] LeafNodeProgress event session %s, node %s", e.Session, e.LeafNode.GetName()) payload, payloadError = buildLeafNodeProgressPayload(e) if payloadError != nil { return @@ -285,7 +282,7 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { s.mutex.Unlock() case *dashboardevents.LeafNodeComplete: - log.Println("[TRACE] Got leaf node complete event", *e) + log.Printf("[TRACE] LeafNodeComplete event session %s, node %s", e.Session, e.LeafNode.GetName()) payload, payloadError = buildLeafNodeCompletePayload(e) if payloadError != nil { return @@ -295,7 +292,7 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { s.mutex.Unlock() case *dashboardevents.DashboardChanged: - log.Println("[TRACE] Got dashboard changed event", *e) + log.Println("[TRACE] DashboardChanged event", *e) deletedDashboards := e.DeletedDashboards newDashboards := e.NewDashboards @@ -408,13 +405,13 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { } case *dashboardevents.DashboardError: - log.Println("[TRACE] Got dashboard error event", *e) + log.Println("[TRACE] dashboard error event", *e) case *dashboardevents.DashboardComplete: - log.Println("[TRACE] Got dashboard complete event", *e) + log.Println("[TRACE] dashboard complete event", *e) case *dashboardevents.ExecutionComplete: - log.Println("[TRACE] Got execution complete event", *e) + log.Println("[TRACE] execution complete event", *e) payload, payloadError = buildExecutionCompletePayload(e) if payloadError != nil { return @@ -430,12 +427,12 @@ func (s *Server) HandleWorkspaceUpdate(event dashboardevents.DashboardEvent) { func (s *Server) Init(ctx context.Context) { // Return list of dashboards on connect s.webSocket.HandleConnect(func(session *melody.Session) { - log.Println("[TRACE] Client connected") + log.Println("[TRACE] client connected") s.addSession(session) }) s.webSocket.HandleDisconnect(func(session *melody.Session) { - log.Println("[TRACE] Client disconnected") + log.Println("[TRACE] client disconnected") s.clearSession(ctx, session) }) @@ -445,7 +442,6 @@ func (s *Server) Init(ctx context.Context) { func (s *Server) handleMessageFunc(ctx context.Context) func(session *melody.Session, msg []byte) { return func(session *melody.Session, msg []byte) { - log.Println("[TRACE] Got message", string(msg)) // TODO TEMP GET SESSION ID sessionId := s.getSessionId(session) @@ -453,6 +449,11 @@ func (s *Server) handleMessageFunc(ctx context.Context) func(session *melody.Ses var request ClientRequest // if we could not decode message - ignore if err := json.Unmarshal(msg, &request); err == nil { + + if request.Action != "keep_alive" { + log.Println("[TRACE] message", string(msg)) + } + switch request.Action { case "get_dashboard_metadata": payload, err := buildDashboardMetadataPayload(s.workspace.GetResourceMaps()) diff --git a/steampipeconfig/load_mod.go b/steampipeconfig/load_mod.go index 8429edc9e..04285f488 100644 --- a/steampipeconfig/load_mod.go +++ b/steampipeconfig/load_mod.go @@ -76,6 +76,8 @@ func LoadMod(modPath string, parentRunCtx *parse.RunContext) (mod *modconfig.Mod } // now we have loaded dependencies, set the current mod on the run context runCtx.CurrentMod = mod + runCtx.PushParent(mod) + defer runCtx.PopParent() // if flag is set, create pseudo resources by mapping files var pseudoResources []modconfig.MappableResource diff --git a/steampipeconfig/modconfig/anonymous_resource.go b/steampipeconfig/modconfig/anonymous_resource.go index 7408648d6..9dc838e7a 100644 --- a/steampipeconfig/modconfig/anonymous_resource.go +++ b/steampipeconfig/modconfig/anonymous_resource.go @@ -2,18 +2,39 @@ package modconfig import ( "fmt" + "strings" "github.com/hashicorp/hcl/v2" ) -func GetAnonymousResourceShortName(block *hcl.Block, mod *Mod) string { +func GetAnonymousResourceShortName(block *hcl.Block, parent ModTreeItem) string { var shortName string anonymous := len(block.Labels) == 0 if anonymous { - shortName = mod.GetUniqueName(fmt.Sprintf("anonymous_%s", block.Type)) + shortName = GetUniqueName(block.Type, parent) } else { shortName = block.Labels[0] } return shortName } + +// GetUniqueName returns a name unique within the scope of this execution tree +func GetUniqueName(blockType string, parent ModTreeItem) string { + + // count how many children of this block type the parent has + childCount := 0 + + for _, child := range parent.GetChildren() { + parsedName, err := ParseResourceName(child.Name()) + if err != nil { + // we do not expect this + continue + } + if parsedName.ItemType == blockType { + childCount++ + } + } + sanitisedParentName := strings.Replace(parent.GetUnqualifiedName(), ".", "_", -1) + return fmt.Sprintf("%s_anonymous_%s_%d", sanitisedParentName, blockType, childCount) +} diff --git a/steampipeconfig/modconfig/benchmark.go b/steampipeconfig/modconfig/benchmark.go index 87a9d258c..804ab0f00 100644 --- a/steampipeconfig/modconfig/benchmark.go +++ b/steampipeconfig/modconfig/benchmark.go @@ -43,8 +43,8 @@ type Benchmark struct { parents []ModTreeItem } -func NewBenchmark(block *hcl.Block, mod *Mod) *Benchmark { - shortName := GetAnonymousResourceShortName(block, mod) +func NewBenchmark(block *hcl.Block, mod *Mod, parent ModTreeItem) *Benchmark { + shortName := GetAnonymousResourceShortName(block, parent) benchmark := &Benchmark{ ShortName: shortName, FullName: fmt.Sprintf("%s.benchmark.%s", mod.ShortName, shortName), diff --git a/steampipeconfig/modconfig/control.go b/steampipeconfig/modconfig/control.go index 390b5a5ac..dd99db2ad 100644 --- a/steampipeconfig/modconfig/control.go +++ b/steampipeconfig/modconfig/control.go @@ -50,8 +50,8 @@ type Control struct { parents []ModTreeItem } -func NewControl(block *hcl.Block, mod *Mod) *Control { - shortName := GetAnonymousResourceShortName(block, mod) +func NewControl(block *hcl.Block, mod *Mod, parent ModTreeItem) *Control { + shortName := GetAnonymousResourceShortName(block, parent) control := &Control{ ShortName: shortName, FullName: fmt.Sprintf("%s.control.%s", mod.ShortName, shortName), diff --git a/steampipeconfig/modconfig/dashboard.go b/steampipeconfig/modconfig/dashboard.go index 42162b47a..4fb801139 100644 --- a/steampipeconfig/modconfig/dashboard.go +++ b/steampipeconfig/modconfig/dashboard.go @@ -49,9 +49,9 @@ type Dashboard struct { HclType string } -func NewDashboard(block *hcl.Block, mod *Mod) *Dashboard { +func NewDashboard(block *hcl.Block, mod *Mod, parent ModTreeItem) *Dashboard { // TODO [reports] think about nested report??? - shortName := GetAnonymousResourceShortName(block, mod) + shortName := GetAnonymousResourceShortName(block, parent) c := &Dashboard{ HclType: block.Type, ShortName: shortName, @@ -83,6 +83,11 @@ func (d *Dashboard) Name() string { // OnDecoded implements HclResource func (d *Dashboard) OnDecoded(block *hcl.Block) hcl.Diagnostics { d.setBaseProperties() + + d.ChildNames = make([]string, len(d.children)) + for i, child := range d.children { + d.ChildNames[i] = child.Name() + } return nil } @@ -200,10 +205,10 @@ func (d *Dashboard) Diff(other *Dashboard) *DashboardTreeItemDiffs { func (d *Dashboard) SetChildren(children []ModTreeItem) { d.children = children - d.ChildNames = make([]string, len(children)) - for i, child := range children { - d.ChildNames[i] = child.Name() - } +} + +func (d *Dashboard) AddChild(child ModTreeItem) { + d.children = append(d.children, child) } // GetUnqualifiedName implements DashboardLeafNode, ModTreeItem diff --git a/steampipeconfig/modconfig/dashboard_card.go b/steampipeconfig/modconfig/dashboard_card.go index b68fd75be..00488f836 100644 --- a/steampipeconfig/modconfig/dashboard_card.go +++ b/steampipeconfig/modconfig/dashboard_card.go @@ -46,8 +46,8 @@ type DashboardCard struct { metadata *ResourceMetadata } -func NewDashboardCard(block *hcl.Block, mod *Mod) *DashboardCard { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardCard(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardCard { + shortName := GetAnonymousResourceShortName(block, parent) c := &DashboardCard{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), diff --git a/steampipeconfig/modconfig/dashboard_chart.go b/steampipeconfig/modconfig/dashboard_chart.go index 8d6cc82c9..58f669cb9 100644 --- a/steampipeconfig/modconfig/dashboard_chart.go +++ b/steampipeconfig/modconfig/dashboard_chart.go @@ -52,8 +52,8 @@ type DashboardChart struct { parents []ModTreeItem } -func NewDashboardChart(block *hcl.Block, mod *Mod) *DashboardChart { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardChart(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardChart { + shortName := GetAnonymousResourceShortName(block, parent) c := &DashboardChart{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), diff --git a/steampipeconfig/modconfig/dashboard_container.go b/steampipeconfig/modconfig/dashboard_container.go index 5a162dd47..a5f791d2b 100644 --- a/steampipeconfig/modconfig/dashboard_container.go +++ b/steampipeconfig/modconfig/dashboard_container.go @@ -37,8 +37,8 @@ type DashboardContainer struct { runtimeDependencyGraph *topsort.Graph } -func NewDashboardContainer(block *hcl.Block, mod *Mod) *DashboardContainer { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardContainer(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardContainer { + shortName := GetAnonymousResourceShortName(block, parent) c := &DashboardContainer{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), @@ -67,7 +67,11 @@ func (c *DashboardContainer) Name() string { } // OnDecoded implements HclResource -func (c *DashboardContainer) OnDecoded(block *hcl.Block) hcl.Diagnostics { +func (c *DashboardContainer) OnDecoded(*hcl.Block) hcl.Diagnostics { + c.ChildNames = make([]string, len(c.children)) + for i, child := range c.children { + c.ChildNames[i] = child.Name() + } return nil } @@ -160,10 +164,10 @@ func (c *DashboardContainer) Diff(other *DashboardContainer) *DashboardTreeItemD func (c *DashboardContainer) SetChildren(children []ModTreeItem) { c.children = children - c.ChildNames = make([]string, len(children)) - for i, child := range children { - c.ChildNames[i] = child.Name() - } +} + +func (c *DashboardContainer) AddChild(child ModTreeItem) { + c.children = append(c.children, child) } // GetUnqualifiedName implements DashboardLeafNode, ModTreeItem diff --git a/steampipeconfig/modconfig/dashboard_hierarchy.go b/steampipeconfig/modconfig/dashboard_hierarchy.go index 498b40630..f49949d21 100644 --- a/steampipeconfig/modconfig/dashboard_hierarchy.go +++ b/steampipeconfig/modconfig/dashboard_hierarchy.go @@ -48,8 +48,8 @@ type DashboardHierarchy struct { parents []ModTreeItem } -func NewDashboardHierarchy(block *hcl.Block, mod *Mod) *DashboardHierarchy { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardHierarchy(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardHierarchy { + shortName := GetAnonymousResourceShortName(block, parent) h := &DashboardHierarchy{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), diff --git a/steampipeconfig/modconfig/dashboard_image.go b/steampipeconfig/modconfig/dashboard_image.go index 32e7f7271..b7cbdc275 100644 --- a/steampipeconfig/modconfig/dashboard_image.go +++ b/steampipeconfig/modconfig/dashboard_image.go @@ -44,8 +44,8 @@ type DashboardImage struct { parents []ModTreeItem } -func NewDashboardImage(block *hcl.Block, mod *Mod) *DashboardImage { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardImage(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardImage { + shortName := GetAnonymousResourceShortName(block, parent) i := &DashboardImage{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), diff --git a/steampipeconfig/modconfig/dashboard_input.go b/steampipeconfig/modconfig/dashboard_input.go index 0d9b864d7..52ceb2ca9 100644 --- a/steampipeconfig/modconfig/dashboard_input.go +++ b/steampipeconfig/modconfig/dashboard_input.go @@ -72,7 +72,7 @@ func (i *DashboardInput) Clone() *DashboardInput { } -func NewDashboardInput(block *hcl.Block, mod *Mod) *DashboardInput { +func NewDashboardInput(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardInput { // input cannot be anonymous shortName := block.Labels[0] i := &DashboardInput{ diff --git a/steampipeconfig/modconfig/dashboard_table.go b/steampipeconfig/modconfig/dashboard_table.go index 1bd89720d..0d2335e76 100644 --- a/steampipeconfig/modconfig/dashboard_table.go +++ b/steampipeconfig/modconfig/dashboard_table.go @@ -47,8 +47,8 @@ type DashboardTable struct { parents []ModTreeItem } -func NewDashboardTable(block *hcl.Block, mod *Mod) *DashboardTable { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardTable(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardTable { + shortName := GetAnonymousResourceShortName(block, parent) t := &DashboardTable{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), diff --git a/steampipeconfig/modconfig/dashboard_text.go b/steampipeconfig/modconfig/dashboard_text.go index edb1c9448..ff87fa2d4 100644 --- a/steampipeconfig/modconfig/dashboard_text.go +++ b/steampipeconfig/modconfig/dashboard_text.go @@ -34,8 +34,8 @@ type DashboardText struct { parents []ModTreeItem } -func NewDashboardText(block *hcl.Block, mod *Mod) *DashboardText { - shortName := GetAnonymousResourceShortName(block, mod) +func NewDashboardText(block *hcl.Block, mod *Mod, parent ModTreeItem) *DashboardText { + shortName := GetAnonymousResourceShortName(block, parent) t := &DashboardText{ ShortName: shortName, FullName: fmt.Sprintf("%s.%s.%s", mod.ShortName, block.Type, shortName), diff --git a/steampipeconfig/modconfig/interfaces.go b/steampipeconfig/modconfig/interfaces.go index 1fc5bf8da..e29209f63 100644 --- a/steampipeconfig/modconfig/interfaces.go +++ b/steampipeconfig/modconfig/interfaces.go @@ -82,7 +82,3 @@ type DashboardLeafNode interface { type ResourceMapsProvider interface { GetResourceMaps() *WorkspaceResourceMaps } - -type UniqueNameProvider interface { - GetUniqueName(string) string -} diff --git a/steampipeconfig/modconfig/mod.go b/steampipeconfig/modconfig/mod.go index da22fa6e4..74f286f43 100644 --- a/steampipeconfig/modconfig/mod.go +++ b/steampipeconfig/modconfig/mod.go @@ -24,7 +24,6 @@ const defaultModName = "local" // Mod is a struct representing a Mod resource type Mod struct { ResourceWithMetadataBase - UniqueNameProviderBase // ShortName is the mod name, e.g. azure_thrifty ShortName string `cty:"short_name" hcl:"name,label"` diff --git a/steampipeconfig/modconfig/unique_name_provider_base.go b/steampipeconfig/modconfig/unique_name_provider_base.go deleted file mode 100644 index 12d0a99ea..000000000 --- a/steampipeconfig/modconfig/unique_name_provider_base.go +++ /dev/null @@ -1,26 +0,0 @@ -package modconfig - -import "fmt" - -type UniqueNameProviderBase struct { - nameMap map[string]struct{} -} - -// GetUniqueName returns a name unique within the scope of this execution tree -func (p *UniqueNameProviderBase) GetUniqueName(name string) string { - if p.nameMap == nil { - p.nameMap = make(map[string]struct{}) - } - // keep adding a suffix until we get a unique name - uniqueName := name - suffix := 0 - for { - if _, ok := p.nameMap[uniqueName]; !ok { - p.nameMap[uniqueName] = struct{}{} - return uniqueName - } - suffix++ - uniqueName = fmt.Sprintf("%s_%d", name, suffix) - } - return uniqueName -} diff --git a/steampipeconfig/parse/decode.go b/steampipeconfig/parse/decode.go index 894df1c7e..5ea77c205 100644 --- a/steampipeconfig/parse/decode.go +++ b/steampipeconfig/parse/decode.go @@ -40,6 +40,7 @@ func decode(runCtx *RunContext) hcl.Diagnostics { Summary: "failed to determine required dependency order", Detail: err.Error()}) } + for _, block := range blocks { resources, res := decodeBlock(block, runCtx) if !res.Success() { @@ -162,33 +163,34 @@ func resourceForBlock(block *hcl.Block, runCtx *RunContext) (modconfig.HclResour var resource modconfig.HclResource // runCtx already contains the current mod mod := runCtx.CurrentMod + parent := runCtx.PeekParent() switch block.Type { case modconfig.BlockTypeMod: resource = mod case modconfig.BlockTypeQuery: resource = modconfig.NewQuery(block, mod) case modconfig.BlockTypeControl: - resource = modconfig.NewControl(block, mod) + resource = modconfig.NewControl(block, mod, parent) case modconfig.BlockTypeBenchmark: - resource = modconfig.NewBenchmark(block, mod) + resource = modconfig.NewBenchmark(block, mod, parent) case modconfig.BlockTypeDashboard: - resource = modconfig.NewDashboard(block, mod) + resource = modconfig.NewDashboard(block, mod, parent) case modconfig.BlockTypeContainer: - resource = modconfig.NewDashboardContainer(block, mod) + resource = modconfig.NewDashboardContainer(block, mod, parent) case modconfig.BlockTypeChart: - resource = modconfig.NewDashboardChart(block, mod) + resource = modconfig.NewDashboardChart(block, mod, parent) case modconfig.BlockTypeCard: - resource = modconfig.NewDashboardCard(block, mod) + resource = modconfig.NewDashboardCard(block, mod, parent) case modconfig.BlockTypeHierarchy: - resource = modconfig.NewDashboardHierarchy(block, mod) + resource = modconfig.NewDashboardHierarchy(block, mod, parent) case modconfig.BlockTypeImage: - resource = modconfig.NewDashboardImage(block, mod) + resource = modconfig.NewDashboardImage(block, mod, parent) case modconfig.BlockTypeInput: - resource = modconfig.NewDashboardInput(block, mod) + resource = modconfig.NewDashboardInput(block, mod, parent) case modconfig.BlockTypeTable: - resource = modconfig.NewDashboardTable(block, mod) + resource = modconfig.NewDashboardTable(block, mod, parent) case modconfig.BlockTypeText: - resource = modconfig.NewDashboardText(block, mod) + resource = modconfig.NewDashboardText(block, mod, parent) default: return nil, hcl.Diagnostics{&hcl.Diagnostic{ Severity: hcl.DiagError, @@ -373,7 +375,7 @@ func invalidParamDiags(resource modconfig.HclResource, block *hcl.Block) *hcl.Di func decodeDashboard(block *hcl.Block, runCtx *RunContext) (*modconfig.Dashboard, *decodeResult) { res := &decodeResult{} - dashboard := modconfig.NewDashboard(block, runCtx.CurrentMod) + dashboard := modconfig.NewDashboard(block, runCtx.CurrentMod, runCtx.PeekParent()) // do a partial decode using QueryProviderBlockSchema // this will be used to pull out attributes which need manual decoding @@ -410,11 +412,16 @@ func decodeDashboard(block *hcl.Block, runCtx *RunContext) (*modconfig.Dashboard func decodeDashboardBlocks(content *hcl.BodyContent, dashboard *modconfig.Dashboard, runCtx *RunContext) *decodeResult { var res = &decodeResult{} - // if children are declared inline as blocks, add them - var children []modconfig.ModTreeItem var inputs []*modconfig.DashboardInput + + // set dashboard as parent on the run context - this is used when generating names for anonymous blocks + runCtx.PushParent(dashboard) + defer func() { + runCtx.PopParent() + }() + for _, b := range content.Blocks { - // use generic block decoding + // decode block resources, blockRes := decodeBlock(b, runCtx) res.Merge(blockRes) if !blockRes.Success() { @@ -434,16 +441,14 @@ func decodeDashboardBlocks(content *hcl.BodyContent, dashboard *modconfig.Dashbo // add the resource to the mod addResourcesToMod(runCtx, resource) - // add to children - // (we expect this cast to always succeed + // add to the dashboard children + // (we expect this cast to always succeed) if child, ok := resource.(modconfig.ModTreeItem); ok { - children = append(children, child) + dashboard.AddChild(child) } - } } - dashboard.SetChildren(children) err := dashboard.SetInputs(inputs) if err != nil { res.addDiags(hcl.Diagnostics{&hcl.Diagnostic{ @@ -459,7 +464,7 @@ func decodeDashboardBlocks(content *hcl.BodyContent, dashboard *modconfig.Dashbo func decodeDashboardContainer(block *hcl.Block, runCtx *RunContext) (*modconfig.DashboardContainer, *decodeResult) { res := &decodeResult{} - container := modconfig.NewDashboardContainer(block, runCtx.CurrentMod) + container := modconfig.NewDashboardContainer(block, runCtx.CurrentMod, runCtx.PeekParent()) // do a partial decode using QueryProviderBlockSchema // this will be used to pull out attributes which need manual decoding @@ -485,8 +490,12 @@ func decodeDashboardContainer(block *hcl.Block, runCtx *RunContext) (*modconfig. func decodeDashboardContainerBlocks(content *hcl.BodyContent, dashboardContainer *modconfig.DashboardContainer, runCtx *RunContext) *decodeResult { var res = &decodeResult{} - // if children are declared inline as blocks, add them - var children []modconfig.ModTreeItem + // set container as parent on the run context - this is used when generating names for anonymous blocks + runCtx.PushParent(dashboardContainer) + defer func() { + runCtx.PopParent() + }() + for _, b := range content.Blocks { // use generic block decoding resources, blockRes := decodeBlock(b, runCtx) @@ -501,21 +510,18 @@ func decodeDashboardContainerBlocks(content *hcl.BodyContent, dashboardContainer addResourcesToMod(runCtx, resource) if child, ok := resource.(modconfig.ModTreeItem); ok { - children = append(children, child) + dashboardContainer.AddChild(child) } - } } - dashboardContainer.SetChildren(children) - return res } func decodeBenchmark(block *hcl.Block, runCtx *RunContext) (*modconfig.Benchmark, *decodeResult) { res := &decodeResult{} - benchmark := modconfig.NewBenchmark(block, runCtx.CurrentMod) + benchmark := modconfig.NewBenchmark(block, runCtx.CurrentMod, runCtx.PeekParent()) content, diags := block.Body.Content(BenchmarkBlockSchema) res.handleDecodeDiags(content, benchmark, diags) diff --git a/steampipeconfig/parse/run_context.go b/steampipeconfig/parse/run_context.go index a0d426075..6bc8fa324 100644 --- a/steampipeconfig/parse/run_context.go +++ b/steampipeconfig/parse/run_context.go @@ -53,6 +53,8 @@ type RunContext struct { BlockTypeExclusions []string Variables map[string]*modconfig.Variable + // stack of parent resources for the currently parsed block + parents []modconfig.ModTreeItem dependencyGraph *topsort.Graph // map of ReferenceTypeValueMaps keyed by mod // NOTE: all values from root mod are keyed with "local" @@ -89,6 +91,21 @@ func (r *RunContext) EnsureWorkspaceLock(mod *modconfig.Mod) error { return nil } +func (r *RunContext) PushParent(parent modconfig.ModTreeItem) { + r.parents = append(r.parents, parent) +} + +func (r *RunContext) PopParent() modconfig.ModTreeItem { + n := len(r.parents) - 1 + res := r.parents[n] + r.parents = r.parents[:n] + return res +} + +func (r *RunContext) PeekParent() modconfig.ModTreeItem { + return r.parents[len(r.parents)-1] +} + // VariableValueMap converts a map of variables to a map of the underlying cty value func VariableValueMap(variables map[string]*modconfig.Variable) map[string]cty.Value { ret := make(map[string]cty.Value, len(variables)) diff --git a/steampipeconfig/testdata/mods/report_test/dashboard.sp b/steampipeconfig/testdata/mods/report_test/dashboard.sp index a4009401f..187945f8a 100644 --- a/steampipeconfig/testdata/mods/report_test/dashboard.sp +++ b/steampipeconfig/testdata/mods/report_test/dashboard.sp @@ -1,110 +1,21 @@ -query "aws_region_input" { - sql = <