-
Notifications
You must be signed in to change notification settings - Fork 963
fix: use resource_id directly for coder_metadata association #18300
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
base: main
Are you sure you want to change the base?
Changes from all commits
9d60f47
1232eaa
6182af4
49a3a2b
f1dabf7
cfb02c7
3d92225
a2afc35
7e0c0a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,6 +208,10 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s | |
// The label is what "terraform graph" uses to reference nodes. | ||
tfResourcesByLabel := map[string]map[string]*tfjson.StateResource{} | ||
|
||
// Map resource IDs to labels for efficient lookup when processing metadata | ||
// Multiple resources can have the same ID, so we store a slice of labels | ||
labelsByResourceID := map[string][]string{} | ||
|
||
// Extra array to preserve the order of rich parameters. | ||
tfResourcesRichParameters := make([]*tfjson.StateResource, 0) | ||
tfResourcesPresets := make([]*tfjson.StateResource, 0) | ||
|
@@ -233,6 +237,13 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s | |
tfResourcesByLabel[label] = map[string]*tfjson.StateResource{} | ||
} | ||
tfResourcesByLabel[label][resource.Address] = resource | ||
|
||
// Build the ID to labels map - multiple resources can have the same ID | ||
if idAttr, hasID := resource.AttributeValues["id"]; hasID { | ||
if idStr, ok := idAttr.(string); ok && idStr != "" { | ||
labelsByResourceID[idStr] = append(labelsByResourceID[idStr], label) | ||
} | ||
} | ||
} | ||
} | ||
for _, module := range modules { | ||
|
@@ -684,41 +695,69 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s | |
if err != nil { | ||
return nil, xerrors.Errorf("decode metadata attributes: %w", err) | ||
} | ||
resourceLabel := convertAddressToLabel(resource.Address) | ||
|
||
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 | ||
var targetLabel string | ||
|
||
// First, check if ResourceID is provided and try to find the resource by ID | ||
if attrs.ResourceID != "" { | ||
// Look for a resource with matching ID | ||
foundLabels := labelsByResourceID[attrs.ResourceID] | ||
switch len(foundLabels) { | ||
case 0: | ||
// If we couldn't find by ID, fall back to graph traversal | ||
logger.Warn(ctx, "coder_metadata resource_id not found, falling back to graph traversal", | ||
slog.F("resource_id", attrs.ResourceID), | ||
slog.F("metadata_address", resource.Address)) | ||
case 1: | ||
// Single match - use it | ||
targetLabel = foundLabels[0] | ||
default: | ||
// Multiple resources with same ID - this creates ambiguity | ||
logger.Warn(ctx, "multiple resources found with same resource_id, falling back to graph traversal", | ||
slog.F("resource_id", attrs.ResourceID), | ||
slog.F("metadata_address", resource.Address), | ||
slog.F("matching_labels", foundLabels)) | ||
Comment on lines
+714
to
+719
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I can see an argument for erroring here to surface the misconfiguration but worry about the potential for breaking existing templates. |
||
} | ||
attachedNode = node | ||
break | ||
} | ||
if attachedNode == nil { | ||
continue | ||
} | ||
var attachedResource *graphResource | ||
for _, resource := range findResourcesInGraph(graph, tfResourcesByLabel, attachedNode.Name, 0, false) { | ||
if attachedResource == nil { | ||
// Default to the first resource because we have nothing to compare! | ||
attachedResource = resource | ||
continue | ||
|
||
// If ResourceID wasn't provided or wasn't found, use graph traversal | ||
if targetLabel == "" { | ||
resourceLabel := convertAddressToLabel(resource.Address) | ||
|
||
var attachedNode *gographviz.Node | ||
for _, node := range graph.Nodes.Lookup { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this stage, it would make sense to extract the "metadata association" piece out and test it separately. |
||
// The node attributes surround the label with quotes. | ||
if strings.Trim(node.Attrs["label"], `"`) != resourceLabel { | ||
continue | ||
} | ||
attachedNode = node | ||
break | ||
} | ||
if resource.Depth < attachedResource.Depth { | ||
// There's a closer resource! | ||
attachedResource = resource | ||
if attachedNode == nil { | ||
continue | ||
} | ||
if resource.Depth == attachedResource.Depth && resource.Label < attachedResource.Label { | ||
attachedResource = resource | ||
var attachedResource *graphResource | ||
for _, resource := range findResourcesInGraph(graph, tfResourcesByLabel, attachedNode.Name, 0, false) { | ||
if attachedResource == nil { | ||
// Default to the first resource because we have nothing to compare! | ||
attachedResource = resource | ||
continue | ||
} | ||
if resource.Depth < attachedResource.Depth { | ||
// There's a closer resource! | ||
attachedResource = resource | ||
continue | ||
} | ||
if resource.Depth == attachedResource.Depth && resource.Label < attachedResource.Label { | ||
attachedResource = resource | ||
continue | ||
} | ||
} | ||
if attachedResource == nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what situation would we encounter this? It's not covered in existing or new tests. |
||
continue | ||
} | ||
targetLabel = attachedResource.Label | ||
} | ||
if attachedResource == nil { | ||
continue | ||
} | ||
targetLabel := attachedResource.Label | ||
|
||
if metadataTargetLabels[targetLabel] { | ||
return nil, xerrors.Errorf("duplicate metadata resource: %s", targetLabel) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
terraform { | ||
required_providers { | ||
coder = { | ||
source = "coder/coder" | ||
version = ">=2.0.0" | ||
} | ||
} | ||
} | ||
|
||
resource "null_resource" "example" {} | ||
|
||
resource "coder_metadata" "example" { | ||
resource_id = "non-existent-id" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have another test to check that it falls back to graph traversal when the |
||
depends_on = [null_resource.example] | ||
item { | ||
key = "test" | ||
value = "value" | ||
} | ||
} |
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.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
terraform { | ||
required_providers { | ||
coder = { | ||
source = "coder/coder" | ||
version = ">=2.0.0" | ||
} | ||
} | ||
} | ||
|
||
resource "null_resource" "first" {} | ||
|
||
resource "null_resource" "second" {} | ||
|
||
resource "coder_metadata" "example" { | ||
resource_id = null_resource.second.id | ||
depends_on = [null_resource.first] | ||
item { | ||
key = "test" | ||
value = "value" | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, this warn log will just end up in the server log and won't be especially visible to template authors.
I can see an argument for erroring out here, but the larger argument against is that it would most likely break existing templates. How do we feel about this?