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

Skip to content

fix: Add tests for instance and app association #2198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion provisioner/terraform/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,86 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
}
}

type appAttributes struct {
AgentID string `mapstructure:"agent_id"`
Name string `mapstructure:"name"`
Icon string `mapstructure:"icon"`
URL string `mapstructure:"url"`
Command string `mapstructure:"command"`
RelativePath bool `mapstructure:"relative_path"`
}
// Associate Apps with agents.
for _, resource := range tfResources {
if resource.Type != "coder_app" {
continue
}
var attrs appAttributes
err = mapstructure.Decode(resource.AttributeValues, &attrs)
if err != nil {
return nil, xerrors.Errorf("decode app attributes: %w", err)
}
if attrs.Name == "" {
// Default to the resource name if none is set!
attrs.Name = resource.Name
}
for _, agent := range agents {
if agent.Id != attrs.AgentID {
continue
}
agent.Apps = append(agent.Apps, &proto.App{
Name: attrs.Name,
Command: attrs.Command,
Url: attrs.URL,
Icon: attrs.Icon,
RelativePath: attrs.RelativePath,
})
}
}

for _, resource := range tfResources {
if resource.Mode == tfjson.DataResourceMode {
continue
}
if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" {
continue
}
agents := findAgents(resourceDependencies, agents, convertAddressToLabel(resource.Address))
for _, agent := range agents {
// Didn't use instance identity.
if agent.GetToken() != "" {
continue
}

// These resource types are for automatically associating an instance ID
// with an agent for authentication.
key, isValid := map[string]string{
"google_compute_instance": "instance_id",
"aws_instance": "id",
"azurerm_linux_virtual_machine": "id",
"azurerm_windows_virtual_machine": "id",
}[resource.Type]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm still pretty noobish when it comes to Terraform, but it seems like we're automatically mapping these resource types for all agents. So for instance if there's both a google_compute_instance, and an aws_instance, one would overwrite the other?

I'm sure the above wouldn't be a common use case, but I imagine it would be possible to create a semi-complex Terraform template with multiple providers depending on certain selections? And I imagine this would be a use-case for having multiple agents, are there others?

If my questions make no sense, feel free to disregard 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If my questions make no sense, feel free to disregard 😄

All questions I'm happy to answer!

So for instance if there's both a google_compute_instance, and an aws_instance, one would overwrite the other?

There's a one -> many mapping of resource to agent. If a resource isn't using token authentication, we assume it's using the zero-trust providers we support: *-instance-identity under the auth parameter on an agent. An instance ID can be manually associated with the coder_agent_instance resource, but we automatically associate it with one of the listed resource types. There isn't an explicit reason we need to do this, and it might end up adding more confusion, so it's not impossible we remove it at some point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for clarifying @kylecarbs! I don't think we need to make any changes to it at this point, but good to be aware of it.

if !isValid {
// The resource type doesn't support
// automatically setting the instance ID.
continue
}
instanceIDRaw, valid := resource.AttributeValues[key]
if !valid {
continue
}
instanceID, valid := instanceIDRaw.(string)
if !valid {
continue
}
agent.Auth = &proto.Agent_InstanceId{
InstanceId: instanceID,
}
}

resources = append(resources, &proto.Resource{
Name: resource.Name,
Type: resource.Type,
Agents: findAgents(resourceDependencies, agents, convertAddressToLabel(resource.Address)),
Agents: agents,
})
}

Expand Down
82 changes: 82 additions & 0 deletions provisioner/terraform/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
tfjson "github.com/hashicorp/terraform-json"
"github.com/stretchr/testify/require"

"github.com/coder/coder/cryptorand"
"github.com/coder/coder/provisioner/terraform"
"github.com/coder/coder/provisionersdk/proto"
)
Expand Down Expand Up @@ -74,6 +75,21 @@ func TestConvertResources(t *testing.T) {
Auth: &proto.Agent_Token{},
}},
}},
"multiple-apps": {{
Name: "dev",
Type: "null_resource",
Agents: []*proto.Agent{{
Name: "dev1",
OperatingSystem: "linux",
Architecture: "amd64",
Apps: []*proto.App{{
Name: "app1",
}, {
Name: "app2",
}},
Auth: &proto.Agent_Token{},
}},
}},
} {
folderName := folderName
expected := expected
Expand Down Expand Up @@ -140,3 +156,69 @@ func TestConvertResources(t *testing.T) {
})
}
}

func TestInstanceIDAssociation(t *testing.T) {
t.Parallel()
type tc struct {
Auth string
ResourceType string
InstanceIDKey string
}
for _, tc := range []tc{{
Auth: "google-instance-identity",
ResourceType: "google_compute_instance",
InstanceIDKey: "instance_id",
}, {
Auth: "aws-instance-identity",
ResourceType: "aws_instance",
InstanceIDKey: "id",
}, {
Auth: "azure-instance-identity",
ResourceType: "azurerm_linux_virtual_machine",
InstanceIDKey: "id",
}, {
Auth: "azure-instance-identity",
ResourceType: "azurerm_windows_virtual_machine",
InstanceIDKey: "id",
}} {
tc := tc
t.Run(tc.ResourceType, func(t *testing.T) {
t.Parallel()
instanceID, err := cryptorand.String(12)
require.NoError(t, err)
resources, err := terraform.ConvertResources(&tfjson.StateModule{
Resources: []*tfjson.StateResource{{
Address: "coder_agent.dev",
Type: "coder_agent",
Name: "dev",
AttributeValues: map[string]interface{}{
"arch": "amd64",
"auth": tc.Auth,
},
}, {
Address: tc.ResourceType + ".dev",
Type: tc.ResourceType,
Name: "dev",
DependsOn: []string{"coder_agent.dev"},
AttributeValues: map[string]interface{}{
tc.InstanceIDKey: instanceID,
},
}},
// This is manually created to join the edges.
}, `digraph {
compound = "true"
newrank = "true"
subgraph "root" {
"[root] coder_agent.dev" [label = "coder_agent.dev", shape = "box"]
"[root] `+tc.ResourceType+`.dev" [label = "`+tc.ResourceType+`.dev", shape = "box"]
"[root] `+tc.ResourceType+`.dev" -> "[root] coder_agent.dev"
}
}
`)
require.NoError(t, err)
require.Len(t, resources, 1)
require.Len(t, resources[0].Agents, 1)
require.Equal(t, resources[0].Agents[0].GetInstanceId(), instanceID)
})
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions provisioner/terraform/testdata/multiple-apps/multiple-apps.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.4.2"
}
}
}

resource "coder_agent" "dev1" {
os = "linux"
arch = "amd64"
}

resource "coder_app" "app1" {
agent_id = coder_agent.dev1.id
}

resource "coder_app" "app2" {
agent_id = coder_agent.dev1.id
}

resource "null_resource" "dev" {
depends_on = [
coder_agent.dev1
]
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading