Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a8c4c46

Browse files
committed
fix: Add tests for instance and app association (#2198)
This was regressed in #2187. There was bad testing around this before, and this should prevent a similiar situation from happening again!
1 parent a368e8f commit a8c4c46

14 files changed

+590
-20
lines changed

provisioner/terraform/resources.go

+70-1
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,86 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
120120
}
121121
}
122122

123+
type appAttributes struct {
124+
AgentID string `mapstructure:"agent_id"`
125+
Name string `mapstructure:"name"`
126+
Icon string `mapstructure:"icon"`
127+
URL string `mapstructure:"url"`
128+
Command string `mapstructure:"command"`
129+
RelativePath bool `mapstructure:"relative_path"`
130+
}
131+
// Associate Apps with agents.
132+
for _, resource := range tfResources {
133+
if resource.Type != "coder_app" {
134+
continue
135+
}
136+
var attrs appAttributes
137+
err = mapstructure.Decode(resource.AttributeValues, &attrs)
138+
if err != nil {
139+
return nil, xerrors.Errorf("decode app attributes: %w", err)
140+
}
141+
if attrs.Name == "" {
142+
// Default to the resource name if none is set!
143+
attrs.Name = resource.Name
144+
}
145+
for _, agent := range agents {
146+
if agent.Id != attrs.AgentID {
147+
continue
148+
}
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+
})
156+
}
157+
}
158+
123159
for _, resource := range tfResources {
124160
if resource.Mode == tfjson.DataResourceMode {
125161
continue
126162
}
127163
if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" {
128164
continue
129165
}
166+
agents := findAgents(resourceDependencies, agents, convertAddressToLabel(resource.Address))
167+
for _, agent := range agents {
168+
// Didn't use instance identity.
169+
if agent.GetToken() != "" {
170+
continue
171+
}
172+
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+
}
197+
}
198+
130199
resources = append(resources, &proto.Resource{
131200
Name: resource.Name,
132201
Type: resource.Type,
133-
Agents: findAgents(resourceDependencies, agents, convertAddressToLabel(resource.Address)),
202+
Agents: agents,
134203
})
135204
}
136205

provisioner/terraform/resources_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
tfjson "github.com/hashicorp/terraform-json"
1212
"github.com/stretchr/testify/require"
1313

14+
"github.com/coder/coder/cryptorand"
1415
"github.com/coder/coder/provisioner/terraform"
1516
"github.com/coder/coder/provisionersdk/proto"
1617
)
@@ -74,6 +75,21 @@ func TestConvertResources(t *testing.T) {
7475
Auth: &proto.Agent_Token{},
7576
}},
7677
}},
78+
"multiple-apps": {{
79+
Name: "dev",
80+
Type: "null_resource",
81+
Agents: []*proto.Agent{{
82+
Name: "dev1",
83+
OperatingSystem: "linux",
84+
Architecture: "amd64",
85+
Apps: []*proto.App{{
86+
Name: "app1",
87+
}, {
88+
Name: "app2",
89+
}},
90+
Auth: &proto.Agent_Token{},
91+
}},
92+
}},
7793
} {
7894
folderName := folderName
7995
expected := expected
@@ -140,3 +156,69 @@ func TestConvertResources(t *testing.T) {
140156
})
141157
}
142158
}
159+
160+
func TestInstanceIDAssociation(t *testing.T) {
161+
t.Parallel()
162+
type tc struct {
163+
Auth string
164+
ResourceType string
165+
InstanceIDKey string
166+
}
167+
for _, tc := range []tc{{
168+
Auth: "google-instance-identity",
169+
ResourceType: "google_compute_instance",
170+
InstanceIDKey: "instance_id",
171+
}, {
172+
Auth: "aws-instance-identity",
173+
ResourceType: "aws_instance",
174+
InstanceIDKey: "id",
175+
}, {
176+
Auth: "azure-instance-identity",
177+
ResourceType: "azurerm_linux_virtual_machine",
178+
InstanceIDKey: "id",
179+
}, {
180+
Auth: "azure-instance-identity",
181+
ResourceType: "azurerm_windows_virtual_machine",
182+
InstanceIDKey: "id",
183+
}} {
184+
tc := tc
185+
t.Run(tc.ResourceType, func(t *testing.T) {
186+
t.Parallel()
187+
instanceID, err := cryptorand.String(12)
188+
require.NoError(t, err)
189+
resources, err := terraform.ConvertResources(&tfjson.StateModule{
190+
Resources: []*tfjson.StateResource{{
191+
Address: "coder_agent.dev",
192+
Type: "coder_agent",
193+
Name: "dev",
194+
AttributeValues: map[string]interface{}{
195+
"arch": "amd64",
196+
"auth": tc.Auth,
197+
},
198+
}, {
199+
Address: tc.ResourceType + ".dev",
200+
Type: tc.ResourceType,
201+
Name: "dev",
202+
DependsOn: []string{"coder_agent.dev"},
203+
AttributeValues: map[string]interface{}{
204+
tc.InstanceIDKey: instanceID,
205+
},
206+
}},
207+
// This is manually created to join the edges.
208+
}, `digraph {
209+
compound = "true"
210+
newrank = "true"
211+
subgraph "root" {
212+
"[root] coder_agent.dev" [label = "coder_agent.dev", shape = "box"]
213+
"[root] `+tc.ResourceType+`.dev" [label = "`+tc.ResourceType+`.dev", shape = "box"]
214+
"[root] `+tc.ResourceType+`.dev" -> "[root] coder_agent.dev"
215+
}
216+
}
217+
`)
218+
require.NoError(t, err)
219+
require.Len(t, resources, 1)
220+
require.Len(t, resources[0].Agents, 1)
221+
require.Equal(t, resources[0].Agents[0].GetInstanceId(), instanceID)
222+
})
223+
}
224+
}

provisioner/terraform/testdata/calling-module/calling-module.tfstate.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

provisioner/terraform/testdata/instance-id/instance-id.tfstate.json

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
version = "0.4.2"
6+
}
7+
}
8+
}
9+
10+
resource "coder_agent" "dev1" {
11+
os = "linux"
12+
arch = "amd64"
13+
}
14+
15+
resource "coder_app" "app1" {
16+
agent_id = coder_agent.dev1.id
17+
}
18+
19+
resource "coder_app" "app2" {
20+
agent_id = coder_agent.dev1.id
21+
}
22+
23+
resource "null_resource" "dev" {
24+
depends_on = [
25+
coder_agent.dev1
26+
]
27+
}

provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.dot

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)