2022-06-08 22:40:34 +00:00
package terraform
import (
"strings"
"github.com/awalterschulze/gographviz"
tfjson "github.com/hashicorp/terraform-json"
"github.com/mitchellh/mapstructure"
"golang.org/x/xerrors"
2023-01-17 10:22:11 +00:00
"github.com/coder/terraform-provider-coder/provider"
2022-10-28 17:41:31 +00:00
"github.com/coder/coder/provisioner"
2022-06-08 22:40:34 +00:00
"github.com/coder/coder/provisionersdk/proto"
)
2022-06-10 15:47:36 +00:00
// A mapping of attributes on the "coder_agent" resource.
type agentAttributes struct {
2023-01-24 12:24:27 +00:00
Auth string ` mapstructure:"auth" `
OperatingSystem string ` mapstructure:"os" `
Architecture string ` mapstructure:"arch" `
Directory string ` mapstructure:"dir" `
ID string ` mapstructure:"id" `
Token string ` mapstructure:"token" `
Env map [ string ] string ` mapstructure:"env" `
StartupScript string ` mapstructure:"startup_script" `
ConnectionTimeoutSeconds int32 ` mapstructure:"connection_timeout" `
TroubleshootingURL string ` mapstructure:"troubleshooting_url" `
MOTDFile string ` mapstructure:"motd_file" `
2023-01-27 20:07:47 +00:00
LoginBeforeReady bool ` mapstructure:"login_before_ready" `
2023-01-24 12:24:27 +00:00
StartupScriptTimeoutSeconds int32 ` mapstructure:"startup_script_timeout" `
2022-06-10 15:47:36 +00:00
}
// A mapping of attributes on the "coder_app" resource.
type agentAppAttributes struct {
2022-10-28 17:41:31 +00:00
AgentID string ` mapstructure:"agent_id" `
// Slug is required in terraform, but to avoid breaking existing users we
// will default to the resource name if it is not specified.
Slug string ` mapstructure:"slug" `
DisplayName string ` mapstructure:"display_name" `
// Name is deprecated in favor of DisplayName.
2022-10-06 14:30:10 +00:00
Name string ` mapstructure:"name" `
Icon string ` mapstructure:"icon" `
URL string ` mapstructure:"url" `
2022-12-14 21:54:18 +00:00
External bool ` mapstructure:"external" `
2022-10-06 14:30:10 +00:00
Command string ` mapstructure:"command" `
2022-10-14 16:46:38 +00:00
Share string ` mapstructure:"share" `
2022-10-06 14:30:10 +00:00
Subdomain bool ` mapstructure:"subdomain" `
Healthcheck [ ] appHealthcheckAttributes ` mapstructure:"healthcheck" `
2022-09-23 19:51:04 +00:00
}
// A mapping of attributes on the "healthcheck" resource.
type appHealthcheckAttributes struct {
URL string ` mapstructure:"url" `
Interval int32 ` mapstructure:"interval" `
Threshold int32 ` mapstructure:"threshold" `
2022-06-10 15:47:36 +00:00
}
2022-08-01 21:53:05 +00:00
// A mapping of attributes on the "coder_metadata" resource.
type metadataAttributes struct {
ResourceID string ` mapstructure:"resource_id" `
2022-09-09 19:38:00 +00:00
Hide bool ` mapstructure:"hide" `
2022-09-13 14:32:59 +00:00
Icon string ` mapstructure:"icon" `
2022-11-14 17:57:33 +00:00
DailyCost int32 ` mapstructure:"daily_cost" `
2022-08-01 21:53:05 +00:00
Items [ ] metadataItem ` mapstructure:"item" `
}
type metadataItem struct {
Key string ` mapstructure:"key" `
Value string ` mapstructure:"value" `
Sensitive bool ` mapstructure:"sensitive" `
IsNull bool ` mapstructure:"is_null" `
}
2023-01-17 10:22:11 +00:00
// ConvertResourcesAndParameters consumes Terraform state and a GraphViz representation
2022-12-21 18:48:49 +00:00
// produced by `terraform graph` to produce resources consumable by Coder.
2022-09-09 19:38:00 +00:00
// nolint:gocyclo
2023-01-17 10:22:11 +00:00
func ConvertResourcesAndParameters ( modules [ ] * tfjson . StateModule , rawGraph string ) ( [ ] * proto . Resource , [ ] * proto . RichParameter , error ) {
2022-06-08 22:40:34 +00:00
parsedGraph , err := gographviz . ParseString ( rawGraph )
if err != nil {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "parse graph: %w" , err )
2022-06-08 22:40:34 +00:00
}
graph , err := gographviz . NewAnalysedGraph ( parsedGraph )
if err != nil {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "analyze graph: %w" , err )
2022-06-08 22:40:34 +00:00
}
resources := make ( [ ] * proto . Resource , 0 )
2022-06-10 15:47:36 +00:00
resourceAgents := map [ string ] [ ] * proto . Agent { }
2022-06-08 22:40:34 +00:00
2022-12-21 18:48:49 +00:00
// Indexes Terraform resources by their label.
// The label is what "terraform graph" uses to reference nodes.
2022-06-10 15:47:36 +00:00
tfResourceByLabel := map [ string ] * tfjson . StateResource { }
var findTerraformResources func ( mod * tfjson . StateModule )
findTerraformResources = func ( mod * tfjson . StateModule ) {
2022-06-08 22:40:34 +00:00
for _ , module := range mod . ChildModules {
2022-06-10 15:47:36 +00:00
findTerraformResources ( module )
}
for _ , resource := range mod . Resources {
2022-08-01 21:53:05 +00:00
label := convertAddressToLabel ( resource . Address )
// index by label
tfResourceByLabel [ label ] = resource
2022-06-08 22:40:34 +00:00
}
}
2023-01-17 10:22:11 +00:00
for _ , module := range modules {
findTerraformResources ( module )
}
2022-06-08 22:40:34 +00:00
2022-06-10 15:47:36 +00:00
// Find all agents!
2022-12-22 23:20:35 +00:00
agentNames := map [ string ] struct { } { }
2022-06-10 15:47:36 +00:00
for _ , tfResource := range tfResourceByLabel {
if tfResource . Type != "coder_agent" {
2022-06-08 22:40:34 +00:00
continue
}
var attrs agentAttributes
2022-06-10 15:47:36 +00:00
err = mapstructure . Decode ( tfResource . AttributeValues , & attrs )
2022-06-08 22:40:34 +00:00
if err != nil {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "decode agent attributes: %w" , err )
2022-06-08 22:40:34 +00:00
}
2022-12-22 23:20:35 +00:00
if _ , ok := agentNames [ tfResource . Name ] ; ok {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "duplicate agent name: %s" , tfResource . Name )
2022-12-22 23:20:35 +00:00
}
agentNames [ tfResource . Name ] = struct { } { }
2023-01-27 20:07:47 +00:00
// Handling for provider pre-v0.6.10.
loginBeforeReady := true
if _ , ok := tfResource . AttributeValues [ "login_before_ready" ] ; ok {
loginBeforeReady = attrs . LoginBeforeReady
}
2022-06-08 22:40:34 +00:00
agent := & proto . Agent {
2023-01-24 12:24:27 +00:00
Name : tfResource . Name ,
Id : attrs . ID ,
Env : attrs . Env ,
StartupScript : attrs . StartupScript ,
OperatingSystem : attrs . OperatingSystem ,
Architecture : attrs . Architecture ,
Directory : attrs . Directory ,
ConnectionTimeoutSeconds : attrs . ConnectionTimeoutSeconds ,
TroubleshootingUrl : attrs . TroubleshootingURL ,
MotdFile : attrs . MOTDFile ,
2023-01-27 20:07:47 +00:00
LoginBeforeReady : loginBeforeReady ,
2023-01-24 12:24:27 +00:00
StartupScriptTimeoutSeconds : attrs . StartupScriptTimeoutSeconds ,
2022-06-08 22:40:34 +00:00
}
switch attrs . Auth {
case "token" :
agent . Auth = & proto . Agent_Token {
Token : attrs . Token ,
}
default :
2022-06-10 15:47:36 +00:00
// If token authentication isn't specified,
// assume instance auth. It's our only other
// authentication type!
2022-06-08 22:40:34 +00:00
agent . Auth = & proto . Agent_InstanceId { }
}
2022-06-10 15:47:36 +00:00
// The label is used to find the graph node!
agentLabel := convertAddressToLabel ( tfResource . Address )
var agentNode * gographviz . Node
for _ , node := range graph . Nodes . Lookup {
// The node attributes surround the label with quotes.
if strings . Trim ( node . Attrs [ "label" ] , ` " ` ) != agentLabel {
continue
}
agentNode = node
break
}
if agentNode == nil {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "couldn't find node on graph: %q" , agentLabel )
2022-06-10 15:47:36 +00:00
}
var agentResource * graphResource
2022-09-09 19:38:00 +00:00
for _ , resource := range findResourcesInGraph ( graph , tfResourceByLabel , agentNode . Name , 0 , true ) {
2022-06-10 15:47:36 +00:00
if agentResource == nil {
// Default to the first resource because we have nothing to compare!
agentResource = resource
continue
}
if resource . Depth < agentResource . Depth {
// There's a closer resource!
agentResource = resource
continue
}
if resource . Depth == agentResource . Depth && resource . Label < agentResource . Label {
agentResource = resource
continue
}
}
2022-06-11 00:02:49 +00:00
if agentResource == nil {
continue
}
2022-06-10 15:47:36 +00:00
agents , exists := resourceAgents [ agentResource . Label ]
if ! exists {
agents = make ( [ ] * proto . Agent , 0 )
}
agents = append ( agents , agent )
resourceAgents [ agentResource . Label ] = agents
2022-06-08 22:40:34 +00:00
}
// Manually associate agents with instance IDs.
2022-06-10 15:47:36 +00:00
for _ , resource := range tfResourceByLabel {
2022-06-08 22:40:34 +00:00
if resource . Type != "coder_agent_instance" {
continue
}
agentIDRaw , valid := resource . AttributeValues [ "agent_id" ]
if ! valid {
continue
}
agentID , valid := agentIDRaw . ( string )
if ! valid {
continue
}
instanceIDRaw , valid := resource . AttributeValues [ "instance_id" ]
if ! valid {
continue
}
instanceID , valid := instanceIDRaw . ( string )
if ! valid {
continue
}
2022-06-10 15:47:36 +00:00
for _ , agents := range resourceAgents {
for _ , agent := range agents {
if agent . Id != agentID {
continue
}
2022-12-14 18:24:22 +00:00
// Only apply the instance ID if the agent authentication
// type is set to do so. A user ran into a bug where they
// had the instance ID block, but auth was set to "token". See:
// https://github.com/coder/coder/issues/4551#issuecomment-1336293468
switch t := agent . Auth . ( type ) {
case * proto . Agent_Token :
continue
case * proto . Agent_InstanceId :
t . InstanceId = instanceID
2022-06-10 15:47:36 +00:00
}
break
2022-06-08 22:40:34 +00:00
}
}
}
2022-06-09 13:34:24 +00:00
// Associate Apps with agents.
2022-10-28 17:41:31 +00:00
appSlugs := make ( map [ string ] struct { } )
2022-06-10 15:47:36 +00:00
for _ , resource := range tfResourceByLabel {
2022-06-09 13:34:24 +00:00
if resource . Type != "coder_app" {
continue
}
2022-10-28 17:41:31 +00:00
2022-06-10 15:47:36 +00:00
var attrs agentAppAttributes
2022-06-09 13:34:24 +00:00
err = mapstructure . Decode ( resource . AttributeValues , & attrs )
if err != nil {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "decode app attributes: %w" , err )
2022-06-09 13:34:24 +00:00
}
2022-10-28 17:41:31 +00:00
// Default to the resource name if none is set!
if attrs . Slug == "" {
attrs . Slug = resource . Name
2022-06-09 13:34:24 +00:00
}
2022-10-28 17:41:31 +00:00
if attrs . DisplayName == "" {
if attrs . Name != "" {
// Name is deprecated but still accepted.
attrs . DisplayName = attrs . Name
} else {
attrs . DisplayName = attrs . Slug
}
}
if ! provisioner . AppSlugRegex . MatchString ( attrs . Slug ) {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "invalid app slug %q, please update your coder/coder provider to the latest version and specify the slug property on each coder_app" , attrs . Slug )
2022-10-28 17:41:31 +00:00
}
if _ , exists := appSlugs [ attrs . Slug ] ; exists {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "duplicate app slug, they must be unique per template: %q" , attrs . Slug )
2022-10-28 17:41:31 +00:00
}
appSlugs [ attrs . Slug ] = struct { } { }
2022-09-23 19:51:04 +00:00
var healthcheck * proto . Healthcheck
if len ( attrs . Healthcheck ) != 0 {
healthcheck = & proto . Healthcheck {
Url : attrs . Healthcheck [ 0 ] . URL ,
Interval : attrs . Healthcheck [ 0 ] . Interval ,
Threshold : attrs . Healthcheck [ 0 ] . Threshold ,
}
}
2022-10-05 19:23:01 +00:00
2022-10-14 16:46:38 +00:00
sharingLevel := proto . AppSharingLevel_OWNER
switch strings . ToLower ( attrs . Share ) {
case "owner" :
sharingLevel = proto . AppSharingLevel_OWNER
case "authenticated" :
sharingLevel = proto . AppSharingLevel_AUTHENTICATED
case "public" :
sharingLevel = proto . AppSharingLevel_PUBLIC
}
2022-06-10 15:47:36 +00:00
for _ , agents := range resourceAgents {
for _ , agent := range agents {
// Find agents with the matching ID and associate them!
if agent . Id != attrs . AgentID {
continue
}
agent . Apps = append ( agent . Apps , & proto . App {
2022-10-28 17:41:31 +00:00
Slug : attrs . Slug ,
DisplayName : attrs . DisplayName ,
2022-10-14 16:46:38 +00:00
Command : attrs . Command ,
2022-12-14 21:54:18 +00:00
External : attrs . External ,
2022-10-14 16:46:38 +00:00
Url : attrs . URL ,
Icon : attrs . Icon ,
Subdomain : attrs . Subdomain ,
SharingLevel : sharingLevel ,
Healthcheck : healthcheck ,
2022-06-10 15:47:36 +00:00
} )
2022-06-09 13:34:24 +00:00
}
}
}
2022-08-01 21:53:05 +00:00
// Associate metadata blocks with resources.
resourceMetadata := map [ string ] [ ] * proto . Resource_Metadata { }
2022-09-09 19:38:00 +00:00
resourceHidden := map [ string ] bool { }
2022-09-13 14:32:59 +00:00
resourceIcon := map [ string ] string { }
2022-11-14 17:57:33 +00:00
resourceCost := map [ string ] int32 { }
2022-09-09 19:38:00 +00:00
for _ , resource := range tfResourceByLabel {
2022-08-01 21:53:05 +00:00
if resource . Type != "coder_metadata" {
continue
}
2022-12-21 18:48:49 +00:00
2022-08-01 21:53:05 +00:00
var attrs metadataAttributes
err = mapstructure . Decode ( resource . AttributeValues , & attrs )
if err != nil {
2023-01-17 10:22:11 +00:00
return nil , nil , xerrors . Errorf ( "decode metadata attributes: %w" , err )
2022-08-01 21:53:05 +00:00
}
2022-12-21 18:48:49 +00:00
resourceLabel := convertAddressToLabel ( resource . Address )
2022-09-09 19:38:00 +00:00
2022-12-21 18:48:49 +00:00
var attachedNode * gographviz . Node
for _ , node := range graph . Nodes . Lookup {
// The node attributes surround the label with quotes.
if strings . Trim ( node . Attrs [ "label" ] , ` " ` ) != resourceLabel {
continue
2022-09-09 19:38:00 +00:00
}
2022-12-21 18:48:49 +00:00
attachedNode = node
break
}
if attachedNode == nil {
continue
}
var attachedResource * graphResource
for _ , resource := range findResourcesInGraph ( graph , tfResourceByLabel , attachedNode . Name , 0 , false ) {
if attachedResource == nil {
// Default to the first resource because we have nothing to compare!
attachedResource = resource
2022-09-09 19:38:00 +00:00
continue
}
2022-12-21 18:48:49 +00:00
if resource . Depth < attachedResource . Depth {
// There's a closer resource!
attachedResource = resource
continue
2022-09-09 19:38:00 +00:00
}
2022-12-21 18:48:49 +00:00
if resource . Depth == attachedResource . Depth && resource . Label < attachedResource . Label {
attachedResource = resource
2022-09-09 19:38:00 +00:00
continue
}
}
2022-12-21 18:48:49 +00:00
if attachedResource == nil {
2022-09-09 19:38:00 +00:00
continue
2022-08-01 21:53:05 +00:00
}
2022-12-21 18:48:49 +00:00
targetLabel := attachedResource . Label
2022-08-01 21:53:05 +00:00
2022-09-09 19:38:00 +00:00
resourceHidden [ targetLabel ] = attrs . Hide
2022-09-13 14:32:59 +00:00
resourceIcon [ targetLabel ] = attrs . Icon
2022-11-14 17:57:33 +00:00
resourceCost [ targetLabel ] = attrs . DailyCost
2022-08-01 21:53:05 +00:00
for _ , item := range attrs . Items {
resourceMetadata [ targetLabel ] = append ( resourceMetadata [ targetLabel ] ,
& proto . Resource_Metadata {
Key : item . Key ,
Value : item . Value ,
Sensitive : item . Sensitive ,
IsNull : item . IsNull ,
} )
}
}
2022-06-10 15:47:36 +00:00
for _ , resource := range tfResourceByLabel {
2022-06-08 22:40:34 +00:00
if resource . Mode == tfjson . DataResourceMode {
continue
}
2022-08-01 21:53:05 +00:00
if resource . Type == "coder_agent" || resource . Type == "coder_agent_instance" || resource . Type == "coder_app" || resource . Type == "coder_metadata" {
2022-06-08 22:40:34 +00:00
continue
}
2022-08-01 21:53:05 +00:00
label := convertAddressToLabel ( resource . Address )
2022-06-09 13:34:24 +00:00
2022-08-01 21:53:05 +00:00
agents , exists := resourceAgents [ label ]
2022-06-10 15:47:36 +00:00
if exists {
applyAutomaticInstanceID ( resource , agents )
2022-06-09 13:34:24 +00:00
}
2022-06-08 22:40:34 +00:00
resources = append ( resources , & proto . Resource {
2022-11-01 21:51:57 +00:00
Name : resource . Name ,
Type : resource . Type ,
Agents : agents ,
2022-11-14 17:57:33 +00:00
Metadata : resourceMetadata [ label ] ,
2022-11-01 21:51:57 +00:00
Hide : resourceHidden [ label ] ,
Icon : resourceIcon [ label ] ,
2022-11-14 17:57:33 +00:00
DailyCost : resourceCost [ label ] ,
2022-11-01 21:51:57 +00:00
InstanceType : applyInstanceType ( resource ) ,
2022-06-08 22:40:34 +00:00
} )
}
2023-01-17 10:22:11 +00:00
parameters := make ( [ ] * proto . RichParameter , 0 )
for _ , resource := range tfResourceByLabel {
if resource . Type != "coder_parameter" {
continue
}
var param provider . Parameter
err = mapstructure . Decode ( resource . AttributeValues , & param )
if err != nil {
return nil , nil , xerrors . Errorf ( "decode map values for coder_parameter.%s: %w" , resource . Name , err )
}
protoParam := & proto . RichParameter {
Name : param . Name ,
Description : param . Description ,
Type : param . Type ,
Mutable : param . Mutable ,
DefaultValue : param . Default ,
Icon : param . Icon ,
}
if len ( param . Validation ) == 1 {
protoParam . ValidationRegex = param . Validation [ 0 ] . Regex
2023-01-24 13:22:00 +00:00
protoParam . ValidationError = param . Validation [ 0 ] . Error
2023-01-17 10:22:11 +00:00
protoParam . ValidationMax = int32 ( param . Validation [ 0 ] . Max )
protoParam . ValidationMin = int32 ( param . Validation [ 0 ] . Min )
}
if len ( param . Option ) > 0 {
protoParam . Options = make ( [ ] * proto . RichParameterOption , 0 , len ( param . Option ) )
for _ , option := range param . Option {
protoParam . Options = append ( protoParam . Options , & proto . RichParameterOption {
Name : option . Name ,
Description : option . Description ,
Value : option . Value ,
Icon : option . Icon ,
} )
}
}
parameters = append ( parameters , protoParam )
}
return resources , parameters , nil
2022-06-08 22:40:34 +00:00
}
// convertAddressToLabel returns the Terraform address without the count
2022-12-21 18:48:49 +00:00
// specifier.
// eg. "module.ec2_dev.ec2_instance.dev[0]" becomes "module.ec2_dev.ec2_instance.dev"
2022-06-08 22:40:34 +00:00
func convertAddressToLabel ( address string ) string {
2022-12-21 18:48:49 +00:00
cut , _ , _ := strings . Cut ( address , "[" )
return cut
2022-06-08 22:40:34 +00:00
}
2022-06-10 15:47:36 +00:00
type graphResource struct {
Label string
Depth uint
}
2022-11-01 21:51:57 +00:00
// applyInstanceType sets the instance type on an agent if it matches
// one of the special resource types that we track.
func applyInstanceType ( resource * tfjson . StateResource ) string {
key , isValid := map [ string ] string {
"google_compute_instance" : "machine_type" ,
"aws_instance" : "instance_type" ,
"aws_spot_instance_request" : "instance_type" ,
"azurerm_linux_virtual_machine" : "size" ,
"azurerm_windows_virtual_machine" : "size" ,
} [ resource . Type ]
if ! isValid {
return ""
}
instanceTypeRaw , isValid := resource . AttributeValues [ key ]
if ! isValid {
return ""
}
instanceType , isValid := instanceTypeRaw . ( string )
if ! isValid {
return ""
}
return instanceType
}
2022-06-10 15:47:36 +00:00
// applyAutomaticInstanceID checks if the resource is one of a set of *magical* IDs
// that automatically index their identifier for automatic authentication.
func applyAutomaticInstanceID ( resource * tfjson . StateResource , agents [ ] * proto . Agent ) {
// 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" ,
2022-08-04 15:20:56 +00:00
"aws_spot_instance_request" : "spot_instance_id" ,
2022-08-03 17:19:13 +00:00
"azurerm_linux_virtual_machine" : "virtual_machine_id" ,
"azurerm_windows_virtual_machine" : "virtual_machine_id" ,
2022-06-10 15:47:36 +00:00
} [ resource . Type ]
if ! isValid {
return
2022-06-08 22:40:34 +00:00
}
2022-06-10 15:47:36 +00:00
// The resource type doesn't support
// automatically setting the instance ID.
instanceIDRaw , isValid := resource . AttributeValues [ key ]
if ! isValid {
return
}
instanceID , isValid := instanceIDRaw . ( string )
if ! isValid {
return
}
for _ , agent := range agents {
// Didn't use instance identity.
if agent . GetToken ( ) != "" {
2022-06-08 22:40:34 +00:00
continue
}
2022-06-10 15:47:36 +00:00
if agent . GetInstanceId ( ) != "" {
// If an instance ID is manually specified, do not override!
continue
}
agent . Auth = & proto . Agent_InstanceId {
InstanceId : instanceID ,
}
2022-06-08 22:40:34 +00:00
}
}
2022-09-09 19:38:00 +00:00
// findResourcesInGraph traverses directionally in a graph until a resource is found,
2022-06-10 15:47:36 +00:00
// then it stores the depth it was found at, and continues working up the tree.
2022-09-09 19:38:00 +00:00
// nolint:revive
func findResourcesInGraph ( graph * gographviz . Graph , tfResourceByLabel map [ string ] * tfjson . StateResource , nodeName string , currentDepth uint , up bool ) [ ] * graphResource {
2022-06-10 15:47:36 +00:00
graphResources := make ( [ ] * graphResource , 0 )
2022-09-09 19:38:00 +00:00
mapping := graph . Edges . DstToSrcs
if ! up {
mapping = graph . Edges . SrcToDsts
}
for destination := range mapping [ nodeName ] {
2022-06-10 15:47:36 +00:00
destinationNode := graph . Nodes . Lookup [ destination ]
// Work our way up the tree!
2022-09-09 19:38:00 +00:00
graphResources = append ( graphResources , findResourcesInGraph ( graph , tfResourceByLabel , destinationNode . Name , currentDepth + 1 , up ) ... )
2022-06-10 15:47:36 +00:00
destinationLabel , exists := destinationNode . Attrs [ "label" ]
2022-06-08 22:40:34 +00:00
if ! exists {
continue
}
2022-06-10 15:47:36 +00:00
destinationLabel = strings . Trim ( destinationLabel , ` " ` )
resource , exists := tfResourceByLabel [ destinationLabel ]
2022-06-08 22:40:34 +00:00
if ! exists {
continue
}
2022-06-10 15:47:36 +00:00
// Data sources cannot be associated with agents for now!
if resource . Mode != tfjson . ManagedResourceMode {
continue
}
// Don't associate Coder resources with other Coder resources!
if strings . HasPrefix ( resource . Type , "coder_" ) {
continue
}
graphResources = append ( graphResources , & graphResource {
Label : destinationLabel ,
Depth : currentDepth ,
} )
2022-06-08 22:40:34 +00:00
}
2022-06-10 15:47:36 +00:00
return graphResources
2022-06-08 22:40:34 +00:00
}