Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1033852

Browse files
committed
fix: Use explicit resource order when assocating agents
This cleans up agent association code to explicitly map a singleagent to a single resource. This willfix#1884, and unblocka prospect from beginning a POC.
1 parent0ec1e8f commit1033852

21 files changed

+724
-216
lines changed

‎.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"sdktrace",
6464
"Signup",
6565
"sourcemapped",
66+
"Srcs",
6667
"stretchr",
6768
"TCGETS",
6869
"tcpip",

‎provisioner/terraform/resources.go

Lines changed: 176 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@ import (
1111
"github.com/coder/coder/provisionersdk/proto"
1212
)
1313

14+
// A mapping of attributes on the "coder_agent" resource.
15+
typeagentAttributesstruct {
16+
Authstring`mapstructure:"auth"`
17+
OperatingSystemstring`mapstructure:"os"`
18+
Architecturestring`mapstructure:"arch"`
19+
Directorystring`mapstructure:"dir"`
20+
IDstring`mapstructure:"id"`
21+
Tokenstring`mapstructure:"token"`
22+
Envmap[string]string`mapstructure:"env"`
23+
StartupScriptstring`mapstructure:"startup_script"`
24+
}
25+
26+
// A mapping of attributes on the "coder_app" resource.
27+
typeagentAppAttributesstruct {
28+
AgentIDstring`mapstructure:"agent_id"`
29+
Namestring`mapstructure:"name"`
30+
Iconstring`mapstructure:"icon"`
31+
URLstring`mapstructure:"url"`
32+
Commandstring`mapstructure:"command"`
33+
RelativePathbool`mapstructure:"relative_path"`
34+
}
35+
1436
// ConvertResources consumes Terraform state and a GraphViz representation produced by
1537
// `terraform graph` to produce resources consumable by Coder.
1638
funcConvertResources(module*tfjson.StateModule,rawGraphstring) ([]*proto.Resource,error) {
@@ -22,52 +44,36 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
2244
iferr!=nil {
2345
returnnil,xerrors.Errorf("analyze graph: %w",err)
2446
}
25-
resourceDependencies:=map[string][]string{}
26-
for_,node:=rangegraph.Nodes.Nodes {
27-
label,exists:=node.Attrs["label"]
28-
if!exists {
29-
continue
30-
}
31-
label=strings.Trim(label,`"`)
32-
resourceDependencies[label]=findDependenciesWithLabels(graph,node.Name)
33-
}
3447

3548
resources:=make([]*proto.Resource,0)
36-
agents:=map[string]*proto.Agent{}
49+
resourceAgents:=map[string][]*proto.Agent{}
3750

38-
tfResources:=make([]*tfjson.StateResource,0)
39-
varappendResourcesfunc(mod*tfjson.StateModule)
40-
appendResources=func(mod*tfjson.StateModule) {
51+
// Indexes Terraform resources by it's label. The label
52+
// is what "terraform graph" uses to reference nodes.
53+
tfResourceByLabel:=map[string]*tfjson.StateResource{}
54+
varfindTerraformResourcesfunc(mod*tfjson.StateModule)
55+
findTerraformResources=func(mod*tfjson.StateModule) {
4156
for_,module:=rangemod.ChildModules {
42-
appendResources(module)
57+
findTerraformResources(module)
58+
}
59+
for_,resource:=rangemod.Resources {
60+
tfResourceByLabel[convertAddressToLabel(resource.Address)]=resource
4361
}
44-
tfResources=append(tfResources,mod.Resources...)
45-
}
46-
appendResources(module)
47-
48-
typeagentAttributesstruct {
49-
Authstring`mapstructure:"auth"`
50-
OperatingSystemstring`mapstructure:"os"`
51-
Architecturestring`mapstructure:"arch"`
52-
Directorystring`mapstructure:"dir"`
53-
IDstring`mapstructure:"id"`
54-
Tokenstring`mapstructure:"token"`
55-
Envmap[string]string`mapstructure:"env"`
56-
StartupScriptstring`mapstructure:"startup_script"`
5762
}
63+
findTerraformResources(module)
5864

59-
//Store all agents inside the maps!
60-
for_,resource:=rangetfResources {
61-
ifresource.Type!="coder_agent" {
65+
//Find all agents!
66+
for_,tfResource:=rangetfResourceByLabel {
67+
iftfResource.Type!="coder_agent" {
6268
continue
6369
}
6470
varattrsagentAttributes
65-
err=mapstructure.Decode(resource.AttributeValues,&attrs)
71+
err=mapstructure.Decode(tfResource.AttributeValues,&attrs)
6672
iferr!=nil {
6773
returnnil,xerrors.Errorf("decode agent attributes: %w",err)
6874
}
6975
agent:=&proto.Agent{
70-
Name:resource.Name,
76+
Name:tfResource.Name,
7177
Id:attrs.ID,
7278
Env:attrs.Env,
7379
StartupScript:attrs.StartupScript,
@@ -81,14 +87,56 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
8187
Token:attrs.Token,
8288
}
8389
default:
90+
// If token authentication isn't specified,
91+
// assume instance auth. It's our only other
92+
// authentication type!
8493
agent.Auth=&proto.Agent_InstanceId{}
8594
}
8695

87-
agents[convertAddressToLabel(resource.Address)]=agent
96+
// The label is used to find the graph node!
97+
agentLabel:=convertAddressToLabel(tfResource.Address)
98+
99+
varagentNode*gographviz.Node
100+
for_,node:=rangegraph.Nodes.Lookup {
101+
// The node attributes surround the label with quotes.
102+
ifstrings.Trim(node.Attrs["label"],`"`)!=agentLabel {
103+
continue
104+
}
105+
agentNode=node
106+
break
107+
}
108+
ifagentNode==nil {
109+
returnnil,xerrors.Errorf("couldn't find node on graph: %q",agentLabel)
110+
}
111+
112+
varagentResource*graphResource
113+
for_,resource:=rangefindResourcesUpGraph(graph,tfResourceByLabel,agentNode.Name,0) {
114+
ifagentResource==nil {
115+
// Default to the first resource because we have nothing to compare!
116+
agentResource=resource
117+
continue
118+
}
119+
ifresource.Depth<agentResource.Depth {
120+
// There's a closer resource!
121+
agentResource=resource
122+
continue
123+
}
124+
ifresource.Depth==agentResource.Depth&&resource.Label<agentResource.Label {
125+
agentResource=resource
126+
continue
127+
}
128+
}
129+
130+
agents,exists:=resourceAgents[agentResource.Label]
131+
if!exists {
132+
agents=make([]*proto.Agent,0)
133+
}
134+
agents=append(agents,agent)
135+
resourceAgents[agentResource.Label]=agents
88136
}
89137

90138
// Manually associate agents with instance IDs.
91-
for_,resource:=rangetfResources {
139+
for_,resource:=rangetfResourceByLabel {
92140
ifresource.Type!="coder_agent_instance" {
93141
continue
94142
}
@@ -109,31 +157,25 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
109157
continue
110158
}
111159

112-
for_,agent:=rangeagents {
113-
ifagent.Id!=agentID {
114-
continue
160+
for_,agents:=rangeresourceAgents {
161+
for_,agent:=rangeagents {
162+
ifagent.Id!=agentID {
163+
continue
164+
}
165+
agent.Auth=&proto.Agent_InstanceId{
166+
InstanceId:instanceID,
167+
}
168+
break
115169
}
116-
agent.Auth=&proto.Agent_InstanceId{
117-
InstanceId:instanceID,
118-
}
119-
break
120170
}
121171
}
122172

123-
typeappAttributesstruct {
124-
AgentIDstring`mapstructure:"agent_id"`
125-
Namestring`mapstructure:"name"`
126-
Iconstring`mapstructure:"icon"`
127-
URLstring`mapstructure:"url"`
128-
Commandstring`mapstructure:"command"`
129-
RelativePathbool`mapstructure:"relative_path"`
130-
}
131173
// Associate Apps with agents.
132-
for_,resource:=rangetfResources {
174+
for_,resource:=rangetfResourceByLabel {
133175
ifresource.Type!="coder_app" {
134176
continue
135177
}
136-
varattrsappAttributes
178+
varattrsagentAppAttributes
137179
err=mapstructure.Decode(resource.AttributeValues,&attrs)
138180
iferr!=nil {
139181
returnnil,xerrors.Errorf("decode app attributes: %w",err)
@@ -142,58 +184,34 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
142184
// Default to the resource name if none is set!
143185
attrs.Name=resource.Name
144186
}
145-
for_,agent:=rangeagents {
146-
ifagent.Id!=attrs.AgentID {
147-
continue
187+
for_,agents:=rangeresourceAgents {
188+
for_,agent:=rangeagents {
189+
// Find agents with the matching ID and associate them!
190+
ifagent.Id!=attrs.AgentID {
191+
continue
192+
}
193+
agent.Apps=append(agent.Apps,&proto.App{
194+
Name:attrs.Name,
195+
Command:attrs.Command,
196+
Url:attrs.URL,
197+
Icon:attrs.Icon,
198+
RelativePath:attrs.RelativePath,
199+
})
148200
}
149-
agent.Apps=append(agent.Apps,&proto.App{
150-
Name:attrs.Name,
151-
Command:attrs.Command,
152-
Url:attrs.URL,
153-
Icon:attrs.Icon,
154-
RelativePath:attrs.RelativePath,
155-
})
156201
}
157202
}
158203

159-
for_,resource:=rangetfResources {
204+
for_,resource:=rangetfResourceByLabel {
160205
ifresource.Mode==tfjson.DataResourceMode {
161206
continue
162207
}
163208
ifresource.Type=="coder_agent"||resource.Type=="coder_agent_instance"||resource.Type=="coder_app" {
164209
continue
165210
}
166-
agents:=findAgents(resourceDependencies,agents,convertAddressToLabel(resource.Address))
167-
for_,agent:=rangeagents {
168-
// Didn't use instance identity.
169-
ifagent.GetToken()!="" {
170-
continue
171-
}
172211

173-
// These resource types are for automatically associating an instance ID
174-
// with an agent for authentication.
175-
key,isValid:=map[string]string{
176-
"google_compute_instance":"instance_id",
177-
"aws_instance":"id",
178-
"azurerm_linux_virtual_machine":"id",
179-
"azurerm_windows_virtual_machine":"id",
180-
}[resource.Type]
181-
if!isValid {
182-
// The resource type doesn't support
183-
// automatically setting the instance ID.
184-
continue
185-
}
186-
instanceIDRaw,valid:=resource.AttributeValues[key]
187-
if!valid {
188-
continue
189-
}
190-
instanceID,valid:=instanceIDRaw.(string)
191-
if!valid {
192-
continue
193-
}
194-
agent.Auth=&proto.Agent_InstanceId{
195-
InstanceId:instanceID,
196-
}
212+
agents,exists:=resourceAgents[convertAddressToLabel(resource.Address)]
213+
ifexists {
214+
applyAutomaticInstanceID(resource,agents)
197215
}
198216

199217
resources=append(resources,&proto.Resource{
@@ -212,46 +230,83 @@ func convertAddressToLabel(address string) string {
212230
returnstrings.Split(address,"[")[0]
213231
}
214232

215-
// findAgents recursively searches through resource dependencies
216-
// to find associated agents. Nested is required for indirect
217-
// dependency matching.
218-
funcfindAgents(resourceDependenciesmap[string][]string,agentsmap[string]*proto.Agent,resourceLabelstring) []*proto.Agent {
219-
resourceNode,exists:=resourceDependencies[resourceLabel]
220-
if!exists {
221-
return []*proto.Agent{}
233+
typegraphResourcestruct {
234+
Labelstring
235+
Depthuint
236+
}
237+
238+
// applyAutomaticInstanceID checks if the resource is one of a set of *magical* IDs
239+
// that automatically index their identifier for automatic authentication.
240+
funcapplyAutomaticInstanceID(resource*tfjson.StateResource,agents []*proto.Agent) {
241+
// These resource types are for automatically associating an instance ID
242+
// with an agent for authentication.
243+
key,isValid:=map[string]string{
244+
"google_compute_instance":"instance_id",
245+
"aws_instance":"id",
246+
"azurerm_linux_virtual_machine":"id",
247+
"azurerm_windows_virtual_machine":"id",
248+
}[resource.Type]
249+
if!isValid {
250+
return
222251
}
223-
// Associate resources that depend on an agent.
224-
resourceAgents:=make([]*proto.Agent,0)
225-
for_,dep:=rangeresourceNode {
226-
varhasbool
227-
agent,has:=agents[dep]
228-
if!has {
229-
resourceAgents=append(resourceAgents,findAgents(resourceDependencies,agents,dep)...)
252+
253+
// The resource type doesn't support
254+
// automatically setting the instance ID.
255+
instanceIDRaw,isValid:=resource.AttributeValues[key]
256+
if!isValid {
257+
return
258+
}
259+
instanceID,isValid:=instanceIDRaw.(string)
260+
if!isValid {
261+
return
262+
}
263+
for_,agent:=rangeagents {
264+
// Didn't use instance identity.
265+
ifagent.GetToken()!="" {
230266
continue
231267
}
232-
// An agent must be deleted after being assigned so it isn't referenced twice.
233-
delete(agents,dep)
234-
resourceAgents=append(resourceAgents,agent)
268+
ifagent.GetInstanceId()!="" {
269+
// If an instance ID is manually specified, do not override!
270+
continue
271+
}
272+
273+
agent.Auth=&proto.Agent_InstanceId{
274+
InstanceId:instanceID,
275+
}
235276
}
236-
returnresourceAgents
237277
}
238278

239-
// findDependenciesWithLabels recursively finds nodes with labels (resource and data nodes)
240-
// to build a dependency tree.
241-
funcfindDependenciesWithLabels(graph*gographviz.Graph,nodeNamestring) []string {
242-
dependencies:=make([]string,0)
243-
fordestination:=rangegraph.Edges.SrcToDsts[nodeName] {
244-
dependencyNode,exists:=graph.Nodes.Lookup[destination]
279+
// findResourcesUpGraph traverses upwards in a graph until a resource is found,
280+
// then it stores the depth it was found at, and continues working up the tree.
281+
funcfindResourcesUpGraph(graph*gographviz.Graph,tfResourceByLabelmap[string]*tfjson.StateResource,nodeNamestring,currentDepthuint) []*graphResource {
282+
graphResources:=make([]*graphResource,0)
283+
fordestination:=rangegraph.Edges.DstToSrcs[nodeName] {
284+
destinationNode:=graph.Nodes.Lookup[destination]
285+
// Work our way up the tree!
286+
graphResources=append(graphResources,findResourcesUpGraph(graph,tfResourceByLabel,destinationNode.Name,currentDepth+1)...)
287+
288+
destinationLabel,exists:=destinationNode.Attrs["label"]
245289
if!exists {
246290
continue
247291
}
248-
label,exists:=dependencyNode.Attrs["label"]
292+
destinationLabel=strings.Trim(destinationLabel,`"`)
293+
resource,exists:=tfResourceByLabel[destinationLabel]
249294
if!exists {
250-
dependencies=append(dependencies,findDependenciesWithLabels(graph,dependencyNode.Name)...)
251295
continue
252296
}
253-
label=strings.Trim(label,`"`)
254-
dependencies=append(dependencies,label)
297+
// Data sources cannot be associated with agents for now!
298+
ifresource.Mode!=tfjson.ManagedResourceMode {
299+
continue
300+
}
301+
// Don't associate Coder resources with other Coder resources!
302+
ifstrings.HasPrefix(resource.Type,"coder_") {
303+
continue
304+
}
305+
graphResources=append(graphResources,&graphResource{
306+
Label:destinationLabel,
307+
Depth:currentDepth,
308+
})
255309
}
256-
returndependencies
310+
311+
returngraphResources
257312
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp