@@ -1608,3 +1608,179 @@ func sortExternalAuthProviders(providers []*proto.ExternalAuthProviderResource)
1608
1608
return strings .Compare (providers [i ].Id , providers [j ].Id ) == - 1
1609
1609
})
1610
1610
}
1611
+
1612
+ func TestMetadataResourceID (t * testing.T ) {
1613
+ t .Parallel ()
1614
+
1615
+ t .Run ("UsesResourceIDWhenProvided" , func (t * testing.T ) {
1616
+ t .Parallel ()
1617
+ ctx , logger := ctxAndLogger (t )
1618
+
1619
+ // Create a state with two resources and metadata that references the second one via resource_id
1620
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {{
1621
+ Resources : []* tfjson.StateResource {{
1622
+ Address : "null_resource.first" ,
1623
+ Type : "null_resource" ,
1624
+ Name : "first" ,
1625
+ Mode : tfjson .ManagedResourceMode ,
1626
+ AttributeValues : map [string ]interface {}{
1627
+ "id" : "first-resource-id" ,
1628
+ },
1629
+ }, {
1630
+ Address : "null_resource.second" ,
1631
+ Type : "null_resource" ,
1632
+ Name : "second" ,
1633
+ Mode : tfjson .ManagedResourceMode ,
1634
+ AttributeValues : map [string ]interface {}{
1635
+ "id" : "second-resource-id" ,
1636
+ },
1637
+ }, {
1638
+ Address : "coder_metadata.example" ,
1639
+ Type : "coder_metadata" ,
1640
+ Name : "example" ,
1641
+ Mode : tfjson .ManagedResourceMode ,
1642
+ DependsOn : []string {"null_resource.first" },
1643
+ AttributeValues : map [string ]interface {}{
1644
+ "resource_id" : "second-resource-id" ,
1645
+ "item" : []interface {}{
1646
+ map [string ]interface {}{
1647
+ "key" : "test" ,
1648
+ "value" : "value" ,
1649
+ },
1650
+ },
1651
+ },
1652
+ }},
1653
+ }}, `digraph {
1654
+ compound = "true"
1655
+ newrank = "true"
1656
+ subgraph "root" {
1657
+ "[root] null_resource.first" [label = "null_resource.first", shape = "box"]
1658
+ "[root] null_resource.second" [label = "null_resource.second", shape = "box"]
1659
+ "[root] coder_metadata.example" [label = "coder_metadata.example", shape = "box"]
1660
+ "[root] coder_metadata.example" -> "[root] null_resource.first"
1661
+ }
1662
+ }` , logger )
1663
+ require .NoError (t , err )
1664
+ require .Len (t , state .Resources , 2 )
1665
+
1666
+ // Find the resources
1667
+ var firstResource , secondResource * proto.Resource
1668
+ for _ , res := range state .Resources {
1669
+ if res .Name == "first" && res .Type == "null_resource" {
1670
+ firstResource = res
1671
+ } else if res .Name == "second" && res .Type == "null_resource" {
1672
+ secondResource = res
1673
+ }
1674
+ }
1675
+
1676
+ require .NotNil (t , firstResource )
1677
+ require .NotNil (t , secondResource )
1678
+
1679
+ // The metadata should be on the second resource (as specified by resource_id),
1680
+ // not the first one (which is the closest in the graph)
1681
+ require .Len (t , firstResource .Metadata , 0 , "first resource should have no metadata" )
1682
+ require .Len (t , secondResource .Metadata , 1 , "second resource should have metadata" )
1683
+ require .Equal (t , "test" , secondResource .Metadata [0 ].Key )
1684
+ require .Equal (t , "value" , secondResource .Metadata [0 ].Value )
1685
+ })
1686
+
1687
+ t .Run ("FallsBackToGraphWhenResourceIDNotFound" , func (t * testing.T ) {
1688
+ t .Parallel ()
1689
+ ctx , logger := ctxAndLogger (t )
1690
+
1691
+ // Create a state where resource_id references a non-existent ID
1692
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {{
1693
+ Resources : []* tfjson.StateResource {{
1694
+ Address : "null_resource.example" ,
1695
+ Type : "null_resource" ,
1696
+ Name : "example" ,
1697
+ Mode : tfjson .ManagedResourceMode ,
1698
+ AttributeValues : map [string ]interface {}{
1699
+ "id" : "example-resource-id" ,
1700
+ },
1701
+ }, {
1702
+ Address : "coder_metadata.example" ,
1703
+ Type : "coder_metadata" ,
1704
+ Name : "example" ,
1705
+ Mode : tfjson .ManagedResourceMode ,
1706
+ DependsOn : []string {"null_resource.example" },
1707
+ AttributeValues : map [string ]interface {}{
1708
+ "resource_id" : "non-existent-id" ,
1709
+ "item" : []interface {}{
1710
+ map [string ]interface {}{
1711
+ "key" : "test" ,
1712
+ "value" : "value" ,
1713
+ },
1714
+ },
1715
+ },
1716
+ }},
1717
+ }}, `digraph {
1718
+ compound = "true"
1719
+ newrank = "true"
1720
+ subgraph "root" {
1721
+ "[root] null_resource.example" [label = "null_resource.example", shape = "box"]
1722
+ "[root] coder_metadata.example" [label = "coder_metadata.example", shape = "box"]
1723
+ "[root] coder_metadata.example" -> "[root] null_resource.example"
1724
+ }
1725
+ }` , logger )
1726
+ require .NoError (t , err )
1727
+ require .Len (t , state .Resources , 1 )
1728
+
1729
+ // The metadata should still be applied via graph traversal
1730
+ require .Equal (t , "example" , state .Resources [0 ].Name )
1731
+ require .Len (t , state .Resources [0 ].Metadata , 1 )
1732
+ require .Equal (t , "test" , state .Resources [0 ].Metadata [0 ].Key )
1733
+ require .Equal (t , "value" , state .Resources [0 ].Metadata [0 ].Value )
1734
+
1735
+ // When resource_id is not found, it falls back to graph traversal
1736
+ // We can't easily verify the warning was logged without access to the log capture API
1737
+ })
1738
+
1739
+ t .Run ("UsesGraphWhenResourceIDNotProvided" , func (t * testing.T ) {
1740
+ t .Parallel ()
1741
+ ctx , logger := ctxAndLogger (t )
1742
+
1743
+ // Create a state without resource_id
1744
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {{
1745
+ Resources : []* tfjson.StateResource {{
1746
+ Address : "null_resource.example" ,
1747
+ Type : "null_resource" ,
1748
+ Name : "example" ,
1749
+ Mode : tfjson .ManagedResourceMode ,
1750
+ AttributeValues : map [string ]interface {}{
1751
+ "id" : "example-resource-id" ,
1752
+ },
1753
+ }, {
1754
+ Address : "coder_metadata.example" ,
1755
+ Type : "coder_metadata" ,
1756
+ Name : "example" ,
1757
+ Mode : tfjson .ManagedResourceMode ,
1758
+ DependsOn : []string {"null_resource.example" },
1759
+ AttributeValues : map [string ]interface {}{
1760
+ "item" : []interface {}{
1761
+ map [string ]interface {}{
1762
+ "key" : "test" ,
1763
+ "value" : "value" ,
1764
+ },
1765
+ },
1766
+ },
1767
+ }},
1768
+ }}, `digraph {
1769
+ compound = "true"
1770
+ newrank = "true"
1771
+ subgraph "root" {
1772
+ "[root] null_resource.example" [label = "null_resource.example", shape = "box"]
1773
+ "[root] coder_metadata.example" [label = "coder_metadata.example", shape = "box"]
1774
+ "[root] coder_metadata.example" -> "[root] null_resource.example"
1775
+ }
1776
+ }` , logger )
1777
+ require .NoError (t , err )
1778
+ require .Len (t , state .Resources , 1 )
1779
+
1780
+ // The metadata should be applied via graph traversal
1781
+ require .Equal (t , "example" , state .Resources [0 ].Name )
1782
+ require .Len (t , state .Resources [0 ].Metadata , 1 )
1783
+ require .Equal (t , "test" , state .Resources [0 ].Metadata [0 ].Key )
1784
+ require .Equal (t , "value" , state .Resources [0 ].Metadata [0 ].Value )
1785
+ })
1786
+ }
0 commit comments