@@ -1469,3 +1469,179 @@ func sortExternalAuthProviders(providers []*proto.ExternalAuthProviderResource)
1469
1469
return strings .Compare (providers [i ].Id ,providers [j ].Id )== - 1
1470
1470
})
1471
1471
}
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
+ }