@@ -1469,3 +1469,179 @@ func sortExternalAuthProviders(providers []*proto.ExternalAuthProviderResource)
14691469return strings .Compare (providers [i ].Id ,providers [j ].Id )== - 1
14701470})
14711471}
1472+
1473+ func TestMetadataResourceID (t * testing.T ) {
1474+ t .Parallel ()
1475+
1476+ t .Run ("UsesResourceIDWhenProvided" ,func (t * testing.T ) {
1477+ t .Parallel ()
1478+ ctx ,logger := ctxAndLogger (t )
1479+
1480+ // Create a state with two resources and metadata that references the second one via resource_id
1481+ state ,err := terraform .ConvertState (ctx , []* tfjson.StateModule {{
1482+ Resources : []* tfjson.StateResource {{
1483+ Address :"null_resource.first" ,
1484+ Type :"null_resource" ,
1485+ Name :"first" ,
1486+ Mode :tfjson .ManagedResourceMode ,
1487+ AttributeValues :map [string ]interface {}{
1488+ "id" :"first-resource-id" ,
1489+ },
1490+ }, {
1491+ Address :"null_resource.second" ,
1492+ Type :"null_resource" ,
1493+ Name :"second" ,
1494+ Mode :tfjson .ManagedResourceMode ,
1495+ AttributeValues :map [string ]interface {}{
1496+ "id" :"second-resource-id" ,
1497+ },
1498+ }, {
1499+ Address :"coder_metadata.example" ,
1500+ Type :"coder_metadata" ,
1501+ Name :"example" ,
1502+ Mode :tfjson .ManagedResourceMode ,
1503+ DependsOn : []string {"null_resource.first" },
1504+ AttributeValues :map [string ]interface {}{
1505+ "resource_id" :"second-resource-id" ,
1506+ "item" : []interface {}{
1507+ map [string ]interface {}{
1508+ "key" :"test" ,
1509+ "value" :"value" ,
1510+ },
1511+ },
1512+ },
1513+ }},
1514+ }},`digraph {
1515+ compound = "true"
1516+ newrank = "true"
1517+ subgraph "root" {
1518+ "[root] null_resource.first" [label = "null_resource.first", shape = "box"]
1519+ "[root] null_resource.second" [label = "null_resource.second", shape = "box"]
1520+ "[root] coder_metadata.example" [label = "coder_metadata.example", shape = "box"]
1521+ "[root] coder_metadata.example" -> "[root] null_resource.first"
1522+ }
1523+ }` ,logger )
1524+ require .NoError (t ,err )
1525+ require .Len (t ,state .Resources ,2 )
1526+
1527+ // Find the resources
1528+ var firstResource ,secondResource * proto.Resource
1529+ for _ ,res := range state .Resources {
1530+ if res .Name == "first" && res .Type == "null_resource" {
1531+ firstResource = res
1532+ }else if res .Name == "second" && res .Type == "null_resource" {
1533+ secondResource = res
1534+ }
1535+ }
1536+
1537+ require .NotNil (t ,firstResource )
1538+ require .NotNil (t ,secondResource )
1539+
1540+ // The metadata should be on the second resource (as specified by resource_id),
1541+ // not the first one (which is the closest in the graph)
1542+ require .Len (t ,firstResource .Metadata ,0 ,"first resource should have no metadata" )
1543+ require .Len (t ,secondResource .Metadata ,1 ,"second resource should have metadata" )
1544+ require .Equal (t ,"test" ,secondResource .Metadata [0 ].Key )
1545+ require .Equal (t ,"value" ,secondResource .Metadata [0 ].Value )
1546+ })
1547+
1548+ t .Run ("FallsBackToGraphWhenResourceIDNotFound" ,func (t * testing.T ) {
1549+ t .Parallel ()
1550+ ctx ,logger := ctxAndLogger (t )
1551+
1552+ // Create a state where resource_id references a non-existent ID
1553+ state ,err := terraform .ConvertState (ctx , []* tfjson.StateModule {{
1554+ Resources : []* tfjson.StateResource {{
1555+ Address :"null_resource.example" ,
1556+ Type :"null_resource" ,
1557+ Name :"example" ,
1558+ Mode :tfjson .ManagedResourceMode ,
1559+ AttributeValues :map [string ]interface {}{
1560+ "id" :"example-resource-id" ,
1561+ },
1562+ }, {
1563+ Address :"coder_metadata.example" ,
1564+ Type :"coder_metadata" ,
1565+ Name :"example" ,
1566+ Mode :tfjson .ManagedResourceMode ,
1567+ DependsOn : []string {"null_resource.example" },
1568+ AttributeValues :map [string ]interface {}{
1569+ "resource_id" :"non-existent-id" ,
1570+ "item" : []interface {}{
1571+ map [string ]interface {}{
1572+ "key" :"test" ,
1573+ "value" :"value" ,
1574+ },
1575+ },
1576+ },
1577+ }},
1578+ }},`digraph {
1579+ compound = "true"
1580+ newrank = "true"
1581+ subgraph "root" {
1582+ "[root] null_resource.example" [label = "null_resource.example", shape = "box"]
1583+ "[root] coder_metadata.example" [label = "coder_metadata.example", shape = "box"]
1584+ "[root] coder_metadata.example" -> "[root] null_resource.example"
1585+ }
1586+ }` ,logger )
1587+ require .NoError (t ,err )
1588+ require .Len (t ,state .Resources ,1 )
1589+
1590+ // The metadata should still be applied via graph traversal
1591+ require .Equal (t ,"example" ,state .Resources [0 ].Name )
1592+ require .Len (t ,state .Resources [0 ].Metadata ,1 )
1593+ require .Equal (t ,"test" ,state .Resources [0 ].Metadata [0 ].Key )
1594+ require .Equal (t ,"value" ,state .Resources [0 ].Metadata [0 ].Value )
1595+
1596+ // When resource_id is not found, it falls back to graph traversal
1597+ // We can't easily verify the warning was logged without access to the log capture API
1598+ })
1599+
1600+ t .Run ("UsesGraphWhenResourceIDNotProvided" ,func (t * testing.T ) {
1601+ t .Parallel ()
1602+ ctx ,logger := ctxAndLogger (t )
1603+
1604+ // Create a state without resource_id
1605+ state ,err := terraform .ConvertState (ctx , []* tfjson.StateModule {{
1606+ Resources : []* tfjson.StateResource {{
1607+ Address :"null_resource.example" ,
1608+ Type :"null_resource" ,
1609+ Name :"example" ,
1610+ Mode :tfjson .ManagedResourceMode ,
1611+ AttributeValues :map [string ]interface {}{
1612+ "id" :"example-resource-id" ,
1613+ },
1614+ }, {
1615+ Address :"coder_metadata.example" ,
1616+ Type :"coder_metadata" ,
1617+ Name :"example" ,
1618+ Mode :tfjson .ManagedResourceMode ,
1619+ DependsOn : []string {"null_resource.example" },
1620+ AttributeValues :map [string ]interface {}{
1621+ "item" : []interface {}{
1622+ map [string ]interface {}{
1623+ "key" :"test" ,
1624+ "value" :"value" ,
1625+ },
1626+ },
1627+ },
1628+ }},
1629+ }},`digraph {
1630+ compound = "true"
1631+ newrank = "true"
1632+ subgraph "root" {
1633+ "[root] null_resource.example" [label = "null_resource.example", shape = "box"]
1634+ "[root] coder_metadata.example" [label = "coder_metadata.example", shape = "box"]
1635+ "[root] coder_metadata.example" -> "[root] null_resource.example"
1636+ }
1637+ }` ,logger )
1638+ require .NoError (t ,err )
1639+ require .Len (t ,state .Resources ,1 )
1640+
1641+ // The metadata should be applied via graph traversal
1642+ require .Equal (t ,"example" ,state .Resources [0 ].Name )
1643+ require .Len (t ,state .Resources [0 ].Metadata ,1 )
1644+ require .Equal (t ,"test" ,state .Resources [0 ].Metadata [0 ].Key )
1645+ require .Equal (t ,"value" ,state .Resources [0 ].Metadata [0 ].Value )
1646+ })
1647+ }