Path: blob/main/pkg/flow/internal/controller/component_references.go
4095 views
package controller12import (3"fmt"45"github.com/grafana/agent/pkg/flow/internal/dag"6"github.com/grafana/agent/pkg/river/ast"7"github.com/grafana/agent/pkg/river/diag"8"github.com/grafana/agent/pkg/river/vm"9)1011// Traversal describes accessing a sequence of fields relative to a component.12// Traversal only include uninterrupted sequences of field accessors; for an13// expression "component.field_a.field_b.field_c[0].inner_field", the Traversal14// will be (field_a, field_b, field_c).15type Traversal []*ast.Ident1617// Reference describes an River expression reference to a BlockNode.18type Reference struct {19Target BlockNode // BlockNode being referenced2021// Traversal describes which nested field relative to Target is being22// accessed.23Traversal Traversal24}2526// ComponentReferences returns the list of references a component is making to27// other components.28func ComponentReferences(cn dag.Node, g *dag.Graph) ([]Reference, diag.Diagnostics) {29var (30traversals []Traversal3132diags diag.Diagnostics33)3435switch cn := cn.(type) {36case BlockNode:37if cn.Block() != nil {38traversals = expressionsFromBody(cn.Block().Body)39}40}4142refs := make([]Reference, 0, len(traversals))43for _, t := range traversals {44// We use an empty scope to determine if a reference refers to something in45// the stdlib, since vm.Scope.Lookup will search the scope tree + the46// stdlib.47//48// Any call to an stdlib function is ignored.49var emptyScope vm.Scope50if _, ok := emptyScope.Lookup(t[0].Name); ok {51continue52}5354ref, resolveDiags := resolveTraversal(t, g)55diags = append(diags, resolveDiags...)56if resolveDiags.HasErrors() {57continue58}59refs = append(refs, ref)60}6162return refs, diags63}6465// expressionsFromSyntaxBody recurses through body and finds all variable66// references.67func expressionsFromBody(body ast.Body) []Traversal {68var w traversalWalker69ast.Walk(&w, body)7071// Flush after the walk in case there was an in-progress traversal.72w.flush()73return w.traversals74}7576type traversalWalker struct {77traversals []Traversal7879buildTraversal bool // Whether80currentTraversal Traversal // currentTraversal being built.81}8283func (tw *traversalWalker) Visit(node ast.Node) ast.Visitor {84switch n := node.(type) {85case *ast.IdentifierExpr:86// Identifiers always start new traversals. Pop the last one.87tw.flush()88tw.buildTraversal = true89tw.currentTraversal = append(tw.currentTraversal, n.Ident)9091case *ast.AccessExpr:92ast.Walk(tw, n.Value)9394// Fields being accessed should get only added to the traversal if one is95// being built. This will be false for accesses like a().foo.96if tw.buildTraversal {97tw.currentTraversal = append(tw.currentTraversal, n.Name)98}99return nil100101case *ast.IndexExpr:102// Indexing interrupts traversals so we flush after walking the value.103ast.Walk(tw, n.Value)104tw.flush()105ast.Walk(tw, n.Index)106return nil107108case *ast.CallExpr:109// Calls interrupt traversals so we flush after walking the value.110ast.Walk(tw, n.Value)111tw.flush()112for _, arg := range n.Args {113ast.Walk(tw, arg)114}115return nil116}117118return tw119}120121// flush will flush the in-progress traversal to the traversals list and unset122// the buildTraversal state.123func (tw *traversalWalker) flush() {124if tw.buildTraversal && len(tw.currentTraversal) > 0 {125tw.traversals = append(tw.traversals, tw.currentTraversal)126}127tw.buildTraversal = false128tw.currentTraversal = nil129}130131func resolveTraversal(t Traversal, g *dag.Graph) (Reference, diag.Diagnostics) {132var (133diags diag.Diagnostics134135partial = ComponentID{t[0].Name}136rem = t[1:]137)138139for {140if n := g.GetByID(partial.String()); n != nil {141return Reference{142Target: n.(BlockNode),143Traversal: rem,144}, nil145}146147if len(rem) == 0 {148// Stop: there's no more elements to look at in the traversal.149break150}151152// Append the next name in the traversal to our partial reference.153partial = append(partial, rem[0].Name)154rem = rem[1:]155}156157diags = append(diags, diag.Diagnostic{158Severity: diag.SeverityLevelError,159Message: fmt.Sprintf("component %q does not exist", partial),160StartPos: ast.StartPos(t[0]).Position(),161EndPos: ast.StartPos(t[len(t)-1]).Position(),162})163return Reference{}, diags164}165166167