@@ -1249,24 +1249,120 @@ func TestAgentNameDuplicate(t *testing.T) {
1249
1249
require .ErrorContains (t , err , "duplicate agent name" )
1250
1250
}
1251
1251
1252
- func TestMetadataResourceDuplicate (t * testing.T ) {
1252
+ func TestMetadata (t * testing.T ) {
1253
1253
t .Parallel ()
1254
- ctx , logger := ctxAndLogger (t )
1255
1254
1256
- // Load the multiple-apps state file and edit it.
1257
- dir := filepath .Join ("testdata" , "resources" , "resource-metadata-duplicate" )
1258
- tfPlanRaw , err := os .ReadFile (filepath .Join (dir , "resource-metadata-duplicate.tfplan.json" ))
1259
- require .NoError (t , err )
1260
- var tfPlan tfjson.Plan
1261
- err = json .Unmarshal (tfPlanRaw , & tfPlan )
1262
- require .NoError (t , err )
1263
- tfPlanGraph , err := os .ReadFile (filepath .Join (dir , "resource-metadata-duplicate.tfplan.dot" ))
1264
- require .NoError (t , err )
1255
+ t .Run ("Duplicate" , func (t * testing.T ) {
1256
+ t .Parallel ()
1257
+ ctx , logger := ctxAndLogger (t )
1258
+ // Load the multiple-apps state file and edit it.
1259
+ dir := filepath .Join ("testdata" , "resources" , "resource-metadata-duplicate" )
1260
+ tfPlanRaw , err := os .ReadFile (filepath .Join (dir , "resource-metadata-duplicate.tfplan.json" ))
1261
+ require .NoError (t , err )
1262
+ var tfPlan tfjson.Plan
1263
+ err = json .Unmarshal (tfPlanRaw , & tfPlan )
1264
+ require .NoError (t , err )
1265
+ tfPlanGraph , err := os .ReadFile (filepath .Join (dir , "resource-metadata-duplicate.tfplan.dot" ))
1266
+ require .NoError (t , err )
1265
1267
1266
- state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {tfPlan .PlannedValues .RootModule }, string (tfPlanGraph ), logger )
1267
- require .Nil (t , state )
1268
- require .Error (t , err )
1269
- require .ErrorContains (t , err , "duplicate metadata resource: null_resource.about" )
1268
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {tfPlan .PlannedValues .RootModule }, string (tfPlanGraph ), logger )
1269
+ require .Nil (t , state )
1270
+ require .Error (t , err )
1271
+ require .ErrorContains (t , err , "duplicate metadata resource: null_resource.about" )
1272
+ })
1273
+
1274
+ t .Run ("ResourceID" , func (t * testing.T ) {
1275
+ t .Parallel ()
1276
+
1277
+ t .Run ("ResourceIDProvided" , func (t * testing.T ) {
1278
+ t .Parallel ()
1279
+ ctx , logger := ctxAndLogger (t )
1280
+
1281
+ dir := filepath .Join ("testdata" , "resources" , "resource-id-provided" )
1282
+ tfStateRaw , err := os .ReadFile (filepath .Join (dir , "resource-id-provided.tfstate.json" ))
1283
+ require .NoError (t , err )
1284
+ var tfState tfjson.State
1285
+ err = json .Unmarshal (tfStateRaw , & tfState )
1286
+ require .NoError (t , err )
1287
+ tfStateGraph , err := os .ReadFile (filepath .Join (dir , "resource-id-provided.tfstate.dot" ))
1288
+ require .NoError (t , err )
1289
+
1290
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {tfState .Values .RootModule }, string (tfStateGraph ), logger )
1291
+ require .NoError (t , err )
1292
+ require .Len (t , state .Resources , 2 )
1293
+
1294
+ // Find the resources
1295
+ var firstResource , secondResource * proto.Resource
1296
+ for _ , res := range state .Resources {
1297
+ if res .Name == "first" && res .Type == "null_resource" {
1298
+ firstResource = res
1299
+ } else if res .Name == "second" && res .Type == "null_resource" {
1300
+ secondResource = res
1301
+ }
1302
+ }
1303
+
1304
+ require .NotNil (t , firstResource )
1305
+ require .NotNil (t , secondResource )
1306
+
1307
+ // The metadata should be on the second resource (as specified by resource_id),
1308
+ // not the first one (which is the closest in the graph)
1309
+ require .Len (t , firstResource .Metadata , 0 , "first resource should have no metadata" )
1310
+ require .Len (t , secondResource .Metadata , 1 , "second resource should have metadata" )
1311
+ require .Equal (t , "test" , secondResource .Metadata [0 ].Key )
1312
+ require .Equal (t , "value" , secondResource .Metadata [0 ].Value )
1313
+ })
1314
+
1315
+ t .Run ("ResourceIDNotFound" , func (t * testing.T ) {
1316
+ t .Parallel ()
1317
+ ctx , logger := ctxAndLogger (t )
1318
+
1319
+ dir := filepath .Join ("testdata" , "resources" , "resource-id-not-found" )
1320
+ tfStateRaw , err := os .ReadFile (filepath .Join (dir , "resource-id-not-found.tfstate.json" ))
1321
+ require .NoError (t , err )
1322
+ var tfState tfjson.State
1323
+ err = json .Unmarshal (tfStateRaw , & tfState )
1324
+ require .NoError (t , err )
1325
+ tfStateGraph , err := os .ReadFile (filepath .Join (dir , "resource-id-not-found.tfstate.dot" ))
1326
+ require .NoError (t , err )
1327
+
1328
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {tfState .Values .RootModule }, string (tfStateGraph ), logger )
1329
+ require .NoError (t , err )
1330
+ require .Len (t , state .Resources , 1 )
1331
+
1332
+ // The metadata should still be applied via graph traversal
1333
+ require .Equal (t , "example" , state .Resources [0 ].Name )
1334
+ require .Len (t , state .Resources [0 ].Metadata , 1 )
1335
+ require .Equal (t , "test" , state .Resources [0 ].Metadata [0 ].Key )
1336
+ require .Equal (t , "value" , state .Resources [0 ].Metadata [0 ].Value )
1337
+
1338
+ // When resource_id is not found, it falls back to graph traversal
1339
+ // We can't easily verify the warning was logged without access to the log capture API
1340
+ })
1341
+
1342
+ t .Run ("ResourceIDNotProvided" , func (t * testing.T ) {
1343
+ t .Parallel ()
1344
+ ctx , logger := ctxAndLogger (t )
1345
+
1346
+ dir := filepath .Join ("testdata" , "resources" , "resource-id-not-provided" )
1347
+ tfStateRaw , err := os .ReadFile (filepath .Join (dir , "resource-id-not-provided.tfstate.json" ))
1348
+ require .NoError (t , err )
1349
+ var tfState tfjson.State
1350
+ err = json .Unmarshal (tfStateRaw , & tfState )
1351
+ require .NoError (t , err )
1352
+ tfStateGraph , err := os .ReadFile (filepath .Join (dir , "resource-id-not-provided.tfstate.dot" ))
1353
+ require .NoError (t , err )
1354
+
1355
+ state , err := terraform .ConvertState (ctx , []* tfjson.StateModule {tfState .Values .RootModule }, string (tfStateGraph ), logger )
1356
+ require .NoError (t , err )
1357
+ require .Len (t , state .Resources , 1 )
1358
+
1359
+ // The metadata should be applied via graph traversal
1360
+ require .Equal (t , "example" , state .Resources [0 ].Name )
1361
+ require .Len (t , state .Resources [0 ].Metadata , 1 )
1362
+ require .Equal (t , "test" , state .Resources [0 ].Metadata [0 ].Key )
1363
+ require .Equal (t , "value" , state .Resources [0 ].Metadata [0 ].Value )
1364
+ })
1365
+ })
1270
1366
}
1271
1367
1272
1368
func TestParameterValidation (t * testing.T ) {
@@ -1608,179 +1704,3 @@ func sortExternalAuthProviders(providers []*proto.ExternalAuthProviderResource)
1608
1704
return strings .Compare (providers [i ].Id , providers [j ].Id ) == - 1
1609
1705
})
1610
1706
}
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