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

Skip to content

Commit 439f812

Browse files
committed
fix: use resource_id directly for coder_metadata association
Previously, coder_metadata resources would associate with the wrong Terraform resource because the implementation relied on graph traversal instead of using the explicit resource_id attribute. This fix: - Builds a map from resource IDs to their labels for direct lookup - Uses the resource_id attribute to find the target resource directly - Falls back to graph traversal only if resource_id lookup fails - Adds logging when resource_id is not found in the state This ensures that metadata is correctly associated with the intended resource, even when there are complex dependencies in the Terraform configuration. Fixes the issue where coder_metadata would incorrectly associate with resources it references in its values rather than the resource specified in resource_id.
1 parent 44fff54 commit 439f812

File tree

5 files changed

+203
-24
lines changed

5 files changed

+203
-24
lines changed

provisioner/terraform/resources.go

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,18 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
628628
}
629629
}
630630

631+
// Build a map from resource IDs to their labels for direct lookup
632+
resourceIDToLabel := map[string]string{}
633+
for label, resources := range tfResourcesByLabel {
634+
for _, resource := range resources {
635+
if resource.AttributeValues["id"] != nil {
636+
if id, ok := resource.AttributeValues["id"].(string); ok && id != "" {
637+
resourceIDToLabel[id] = label
638+
}
639+
}
640+
}
641+
}
642+
631643
// Associate metadata blocks with resources.
632644
resourceMetadata := map[string][]*proto.Resource_Metadata{}
633645
resourceHidden := map[string]bool{}
@@ -646,41 +658,63 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
646658
if err != nil {
647659
return nil, xerrors.Errorf("decode metadata attributes: %w", err)
648660
}
649-
resourceLabel := convertAddressToLabel(resource.Address)
650661

651-
var attachedNode *gographviz.Node
652-
for _, node := range graph.Nodes.Lookup {
653-
// The node attributes surround the label with quotes.
654-
if strings.Trim(node.Attrs["label"], `"`) != resourceLabel {
655-
continue
662+
// First, try to use the resource_id directly
663+
var targetLabel string
664+
if attrs.ResourceID != "" {
665+
// Look up the label by resource ID
666+
if label, ok := resourceIDToLabel[attrs.ResourceID]; ok {
667+
targetLabel = label
668+
} else {
669+
// If we can't find the resource by ID, log a warning and fall back to graph traversal
670+
logger.Warn(ctx, "coder_metadata resource_id not found in state",
671+
slog.F("resource_id", attrs.ResourceID),
672+
slog.F("metadata_resource", resource.Address))
656673
}
657-
attachedNode = node
658-
break
659-
}
660-
if attachedNode == nil {
661-
continue
662674
}
663-
var attachedResource *graphResource
664-
for _, resource := range findResourcesInGraph(graph, tfResourcesByLabel, attachedNode.Name, 0, false) {
665-
if attachedResource == nil {
666-
// Default to the first resource because we have nothing to compare!
667-
attachedResource = resource
668-
continue
675+
676+
// Fall back to graph traversal if resource_id lookup failed or was not provided
677+
if targetLabel == "" {
678+
resourceLabel := convertAddressToLabel(resource.Address)
679+
680+
var attachedNode *gographviz.Node
681+
for _, node := range graph.Nodes.Lookup {
682+
// The node attributes surround the label with quotes.
683+
if strings.Trim(node.Attrs["label"], `"`) != resourceLabel {
684+
continue
685+
}
686+
attachedNode = node
687+
break
669688
}
670-
if resource.Depth < attachedResource.Depth {
671-
// There's a closer resource!
672-
attachedResource = resource
689+
if attachedNode == nil {
673690
continue
674691
}
675-
if resource.Depth == attachedResource.Depth && resource.Label < attachedResource.Label {
676-
attachedResource = resource
692+
var attachedResource *graphResource
693+
for _, resource := range findResourcesInGraph(graph, tfResourcesByLabel, attachedNode.Name, 0, false) {
694+
if attachedResource == nil {
695+
// Default to the first resource because we have nothing to compare!
696+
attachedResource = resource
697+
continue
698+
}
699+
if resource.Depth < attachedResource.Depth {
700+
// There's a closer resource!
701+
attachedResource = resource
702+
continue
703+
}
704+
if resource.Depth == attachedResource.Depth && resource.Label < attachedResource.Label {
705+
attachedResource = resource
706+
continue
707+
}
708+
}
709+
if attachedResource == nil {
677710
continue
678711
}
712+
targetLabel = attachedResource.Label
679713
}
680-
if attachedResource == nil {
714+
715+
if targetLabel == "" {
681716
continue
682717
}
683-
targetLabel := attachedResource.Label
684718

685719
if metadataTargetLabels[targetLabel] {
686720
return nil, xerrors.Errorf("duplicate metadata resource: %s", targetLabel)

provisioner/terraform/resources_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,23 @@ func TestConvertResources(t *testing.T) {
587587
},
588588
},
589589
},
590+
// Tests that coder_metadata uses resource_id directly instead of graph traversal
591+
"metadata-resource-id": {
592+
resources: []*proto.Resource{{
593+
Name: "main",
594+
Type: "null_resource",
595+
Metadata: []*proto.Resource_Metadata{{
596+
Key: "secret_name",
597+
Value: "coder-agent-token",
598+
}, {
599+
Key: "resource_id",
600+
Value: "test-null-resource-id-123",
601+
}},
602+
}, {
603+
Name: "coder_agent_token",
604+
Type: "kubernetes_secret",
605+
}},
606+
},
590607
"rich-parameters": {
591608
resources: []*proto.Resource{{
592609
Name: "dev",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
resource "null_resource" "main" {}
2+
3+
resource "kubernetes_secret" "coder_agent_token" {
4+
metadata {
5+
name = "coder-agent-token"
6+
namespace = "default"
7+
}
8+
9+
data = {
10+
token = "test-token"
11+
}
12+
13+
type = "Opaque"
14+
}
15+
16+
# This metadata should associate with null_resource.main, not kubernetes_secret
17+
resource "coder_metadata" "main_metadata" {
18+
resource_id = null_resource.main.id
19+
item {
20+
key = "secret_name"
21+
value = kubernetes_secret.coder_agent_token.metadata[0].name
22+
}
23+
item {
24+
key = "resource_id"
25+
value = null_resource.main.id
26+
}
27+
}

provisioner/terraform/testdata/resources/metadata-resource-id/metadata-resource-id.tfstate.dot

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

provisioner/terraform/testdata/resources/metadata-resource-id/metadata-resource-id.tfstate.json

Lines changed: 90 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)