Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/flow/internal/controller/component_references.go
4095 views
1
package controller
2
3
import (
4
"fmt"
5
6
"github.com/grafana/agent/pkg/flow/internal/dag"
7
"github.com/grafana/agent/pkg/river/ast"
8
"github.com/grafana/agent/pkg/river/diag"
9
"github.com/grafana/agent/pkg/river/vm"
10
)
11
12
// Traversal describes accessing a sequence of fields relative to a component.
13
// Traversal only include uninterrupted sequences of field accessors; for an
14
// expression "component.field_a.field_b.field_c[0].inner_field", the Traversal
15
// will be (field_a, field_b, field_c).
16
type Traversal []*ast.Ident
17
18
// Reference describes an River expression reference to a BlockNode.
19
type Reference struct {
20
Target BlockNode // BlockNode being referenced
21
22
// Traversal describes which nested field relative to Target is being
23
// accessed.
24
Traversal Traversal
25
}
26
27
// ComponentReferences returns the list of references a component is making to
28
// other components.
29
func ComponentReferences(cn dag.Node, g *dag.Graph) ([]Reference, diag.Diagnostics) {
30
var (
31
traversals []Traversal
32
33
diags diag.Diagnostics
34
)
35
36
switch cn := cn.(type) {
37
case BlockNode:
38
if cn.Block() != nil {
39
traversals = expressionsFromBody(cn.Block().Body)
40
}
41
}
42
43
refs := make([]Reference, 0, len(traversals))
44
for _, t := range traversals {
45
// We use an empty scope to determine if a reference refers to something in
46
// the stdlib, since vm.Scope.Lookup will search the scope tree + the
47
// stdlib.
48
//
49
// Any call to an stdlib function is ignored.
50
var emptyScope vm.Scope
51
if _, ok := emptyScope.Lookup(t[0].Name); ok {
52
continue
53
}
54
55
ref, resolveDiags := resolveTraversal(t, g)
56
diags = append(diags, resolveDiags...)
57
if resolveDiags.HasErrors() {
58
continue
59
}
60
refs = append(refs, ref)
61
}
62
63
return refs, diags
64
}
65
66
// expressionsFromSyntaxBody recurses through body and finds all variable
67
// references.
68
func expressionsFromBody(body ast.Body) []Traversal {
69
var w traversalWalker
70
ast.Walk(&w, body)
71
72
// Flush after the walk in case there was an in-progress traversal.
73
w.flush()
74
return w.traversals
75
}
76
77
type traversalWalker struct {
78
traversals []Traversal
79
80
buildTraversal bool // Whether
81
currentTraversal Traversal // currentTraversal being built.
82
}
83
84
func (tw *traversalWalker) Visit(node ast.Node) ast.Visitor {
85
switch n := node.(type) {
86
case *ast.IdentifierExpr:
87
// Identifiers always start new traversals. Pop the last one.
88
tw.flush()
89
tw.buildTraversal = true
90
tw.currentTraversal = append(tw.currentTraversal, n.Ident)
91
92
case *ast.AccessExpr:
93
ast.Walk(tw, n.Value)
94
95
// Fields being accessed should get only added to the traversal if one is
96
// being built. This will be false for accesses like a().foo.
97
if tw.buildTraversal {
98
tw.currentTraversal = append(tw.currentTraversal, n.Name)
99
}
100
return nil
101
102
case *ast.IndexExpr:
103
// Indexing interrupts traversals so we flush after walking the value.
104
ast.Walk(tw, n.Value)
105
tw.flush()
106
ast.Walk(tw, n.Index)
107
return nil
108
109
case *ast.CallExpr:
110
// Calls interrupt traversals so we flush after walking the value.
111
ast.Walk(tw, n.Value)
112
tw.flush()
113
for _, arg := range n.Args {
114
ast.Walk(tw, arg)
115
}
116
return nil
117
}
118
119
return tw
120
}
121
122
// flush will flush the in-progress traversal to the traversals list and unset
123
// the buildTraversal state.
124
func (tw *traversalWalker) flush() {
125
if tw.buildTraversal && len(tw.currentTraversal) > 0 {
126
tw.traversals = append(tw.traversals, tw.currentTraversal)
127
}
128
tw.buildTraversal = false
129
tw.currentTraversal = nil
130
}
131
132
func resolveTraversal(t Traversal, g *dag.Graph) (Reference, diag.Diagnostics) {
133
var (
134
diags diag.Diagnostics
135
136
partial = ComponentID{t[0].Name}
137
rem = t[1:]
138
)
139
140
for {
141
if n := g.GetByID(partial.String()); n != nil {
142
return Reference{
143
Target: n.(BlockNode),
144
Traversal: rem,
145
}, nil
146
}
147
148
if len(rem) == 0 {
149
// Stop: there's no more elements to look at in the traversal.
150
break
151
}
152
153
// Append the next name in the traversal to our partial reference.
154
partial = append(partial, rem[0].Name)
155
rem = rem[1:]
156
}
157
158
diags = append(diags, diag.Diagnostic{
159
Severity: diag.SeverityLevelError,
160
Message: fmt.Sprintf("component %q does not exist", partial),
161
StartPos: ast.StartPos(t[0]).Position(),
162
EndPos: ast.StartPos(t[len(t)-1]).Position(),
163
})
164
return Reference{}, diags
165
}
166
167