Skip to content

Commit dc89ba9

Browse files
author
cortlyons
committed
Update variable test case to pull value from state
1 parent e764422 commit dc89ba9

File tree

5 files changed

+286
-15
lines changed

5 files changed

+286
-15
lines changed

integration/v4_to_v5/testdata/tiered_cache/expected/tiered_cache.tf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ resource "cloudflare_argo_tiered_caching" "generic" {
4848
}
4949
resource "cloudflare_tiered_cache" "variable" {
5050
zone_id = var.cloudflare_zone_id
51-
value = var.cache_type
51+
value = "off"
52+
}
53+
resource "cloudflare_argo_tiered_caching" "variable" {
54+
zone_id = var.cloudflare_zone_id
55+
value = "off"
5256
}
5357
resource "cloudflare_tiered_cache" "lifecycle" {
5458
zone_id = var.cloudflare_zone_id

internal/resources/tiered_cache/v4_to_v5.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,17 @@ func (m *V4ToV5Migrator) TransformConfig(ctx *transform.Context, block *hclwrite
5555
tfhcl.RenameAttribute(body, "cache_type", "value")
5656

5757
resourceName := tfhcl.GetResourceName(block)
58-
value := tfhcl.ExtractStringFromAttribute(body.GetAttribute("value"))
58+
valueAttr := body.GetAttribute("value")
59+
60+
// Try to get the actual value
61+
var value string
62+
if tfhcl.IsExpressionAttribute(valueAttr) {
63+
// It's a variable reference - look up the actual value in state
64+
value = state.GetResourceAttribute(ctx.StateJSON, "cloudflare_tiered_cache", resourceName, "cache_type")
65+
} else {
66+
// It's a literal value
67+
value = tfhcl.ExtractStringFromAttribute(valueAttr)
68+
}
5969

6070
if value == "smart" {
6171
// cache_type="smart" → value="on" for both resources

internal/resources/tiered_cache/v4_to_v5_test.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,6 @@ resource "cloudflare_tiered_cache" "example" {
6060
resource "cloudflare_argo_tiered_caching" "example" {
6161
zone_id = "test-zone-id"
6262
value = "off"
63-
}`,
64-
},
65-
{
66-
Name: "tiered_cache with variable cache_type",
67-
Input: `
68-
resource "cloudflare_tiered_cache" "example" {
69-
zone_id = "test-zone-id"
70-
cache_type = var.cache_type_value
71-
}`,
72-
Expected: `
73-
resource "cloudflare_tiered_cache" "example" {
74-
zone_id = "test-zone-id"
75-
value = var.cache_type_value
7663
}`,
7764
},
7865
{

internal/transform/state/fields.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,67 @@ func IsEmptyValue(value gjson.Result) bool {
443443
return false
444444
}
445445
}
446+
447+
// GetResourceAttribute retrieves an attribute value from the state for a given resource.
448+
// This is useful when config transformation needs to resolve variable values by looking
449+
// up the actual value that was applied in the state.
450+
//
451+
// Example - Getting the actual cache_type value from state when config uses a variable:
452+
//
453+
// Config:
454+
//
455+
// resource "cloudflare_tiered_cache" "example" {
456+
// zone_id = "test-zone-id"
457+
// cache_type = var.cache_type_value // Variable reference
458+
// }
459+
//
460+
// State JSON:
461+
//
462+
// {
463+
// "resources": [{
464+
// "type": "cloudflare_tiered_cache",
465+
// "name": "example",
466+
// "instances": [{
467+
// "attributes": {
468+
// "zone_id": "test-zone-id",
469+
// "cache_type": "smart" // Actual resolved value
470+
// }
471+
// }]
472+
// }]
473+
// }
474+
//
475+
// Usage:
476+
//
477+
// value := GetResourceAttribute(stateJSON, "cloudflare_tiered_cache", "example", "cache_type")
478+
// // Returns: "smart"
479+
//
480+
// If the resource or attribute doesn't exist, returns an empty string.
481+
func GetResourceAttribute(stateJSON, resourceType, resourceName, attributeName string) string {
482+
if stateJSON == "" {
483+
return ""
484+
}
485+
486+
state := gjson.Parse(stateJSON)
487+
resources := state.Get("resources")
488+
if !resources.Exists() {
489+
return ""
490+
}
491+
492+
var result string
493+
resources.ForEach(func(_, resource gjson.Result) bool {
494+
if resource.Get("type").String() == resourceType &&
495+
resource.Get("name").String() == resourceName {
496+
instances := resource.Get("instances")
497+
if instances.Exists() && len(instances.Array()) > 0 {
498+
attrValue := instances.Get("0.attributes." + attributeName)
499+
if attrValue.Exists() {
500+
result = attrValue.String()
501+
return false // Stop iteration
502+
}
503+
}
504+
}
505+
return true // Continue iteration
506+
})
507+
508+
return result
509+
}

internal/transform/state/fields_test.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,3 +630,209 @@ func TestConvertGjsonToJSON(t *testing.T) {
630630
})
631631
}
632632
}
633+
634+
func TestGetResourceAttribute(t *testing.T) {
635+
tests := []struct {
636+
name string
637+
stateJSON string
638+
resourceType string
639+
resourceName string
640+
attributeName string
641+
expected string
642+
}{
643+
{
644+
name: "Get string attribute from existing resource",
645+
stateJSON: `{
646+
"resources": [
647+
{
648+
"type": "cloudflare_tiered_cache",
649+
"name": "example",
650+
"instances": [
651+
{
652+
"attributes": {
653+
"zone_id": "test-zone-id",
654+
"cache_type": "smart",
655+
"id": "test-id"
656+
}
657+
}
658+
]
659+
}
660+
]
661+
}`,
662+
resourceType: "cloudflare_tiered_cache",
663+
resourceName: "example",
664+
attributeName: "cache_type",
665+
expected: "smart",
666+
},
667+
{
668+
name: "Get attribute from resource with multiple instances",
669+
stateJSON: `{
670+
"resources": [
671+
{
672+
"type": "cloudflare_dns_record",
673+
"name": "test",
674+
"instances": [
675+
{
676+
"attributes": {
677+
"zone_id": "zone1",
678+
"name": "test",
679+
"type": "A",
680+
"content": "192.0.2.1"
681+
}
682+
},
683+
{
684+
"attributes": {
685+
"zone_id": "zone2",
686+
"name": "test2",
687+
"type": "AAAA"
688+
}
689+
}
690+
]
691+
}
692+
]
693+
}`,
694+
resourceType: "cloudflare_dns_record",
695+
resourceName: "test",
696+
attributeName: "type",
697+
expected: "A", // Should get first instance
698+
},
699+
{
700+
name: "Resource does not exist",
701+
stateJSON: `{
702+
"resources": [
703+
{
704+
"type": "cloudflare_tiered_cache",
705+
"name": "example",
706+
"instances": [
707+
{
708+
"attributes": {
709+
"zone_id": "test-zone-id",
710+
"cache_type": "smart"
711+
}
712+
}
713+
]
714+
}
715+
]
716+
}`,
717+
resourceType: "cloudflare_tiered_cache",
718+
resourceName: "nonexistent",
719+
attributeName: "cache_type",
720+
expected: "",
721+
},
722+
{
723+
name: "Attribute does not exist",
724+
stateJSON: `{
725+
"resources": [
726+
{
727+
"type": "cloudflare_tiered_cache",
728+
"name": "example",
729+
"instances": [
730+
{
731+
"attributes": {
732+
"zone_id": "test-zone-id",
733+
"cache_type": "smart"
734+
}
735+
}
736+
]
737+
}
738+
]
739+
}`,
740+
resourceType: "cloudflare_tiered_cache",
741+
resourceName: "example",
742+
attributeName: "nonexistent_attr",
743+
expected: "",
744+
},
745+
{
746+
name: "Empty state JSON",
747+
stateJSON: `{
748+
"resources": []
749+
}`,
750+
resourceType: "cloudflare_tiered_cache",
751+
resourceName: "example",
752+
attributeName: "cache_type",
753+
expected: "",
754+
},
755+
{
756+
name: "Empty string as state",
757+
stateJSON: "",
758+
resourceType: "cloudflare_tiered_cache",
759+
resourceName: "example",
760+
attributeName: "cache_type",
761+
expected: "",
762+
},
763+
{
764+
name: "Multiple resources, find correct one",
765+
stateJSON: `{
766+
"resources": [
767+
{
768+
"type": "cloudflare_tiered_cache",
769+
"name": "first",
770+
"instances": [
771+
{
772+
"attributes": {
773+
"cache_type": "off"
774+
}
775+
}
776+
]
777+
},
778+
{
779+
"type": "cloudflare_tiered_cache",
780+
"name": "second",
781+
"instances": [
782+
{
783+
"attributes": {
784+
"cache_type": "smart"
785+
}
786+
}
787+
]
788+
},
789+
{
790+
"type": "cloudflare_tiered_cache",
791+
"name": "third",
792+
"instances": [
793+
{
794+
"attributes": {
795+
"cache_type": "generic"
796+
}
797+
}
798+
]
799+
}
800+
]
801+
}`,
802+
resourceType: "cloudflare_tiered_cache",
803+
resourceName: "second",
804+
attributeName: "cache_type",
805+
expected: "smart",
806+
},
807+
{
808+
name: "Get nested attribute path",
809+
stateJSON: `{
810+
"resources": [
811+
{
812+
"type": "cloudflare_load_balancer",
813+
"name": "example",
814+
"instances": [
815+
{
816+
"attributes": {
817+
"zone_id": "test-zone-id",
818+
"id": "test-id"
819+
}
820+
}
821+
]
822+
}
823+
]
824+
}`,
825+
resourceType: "cloudflare_load_balancer",
826+
resourceName: "example",
827+
attributeName: "zone_id",
828+
expected: "test-zone-id",
829+
},
830+
}
831+
832+
for _, tt := range tests {
833+
t.Run(tt.name, func(t *testing.T) {
834+
result := GetResourceAttribute(tt.stateJSON, tt.resourceType, tt.resourceName, tt.attributeName)
835+
assert.Equal(t, tt.expected, result)
836+
})
837+
}
838+
}

0 commit comments

Comments
 (0)