diff --git a/configs/configupgrade/test-fixtures/valid/element-of-set/input/element-of-set.tf b/configs/configupgrade/test-fixtures/valid/element-of-set/input/element-of-set.tf new file mode 100644 index 000000000..7d64a6956 --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/element-of-set/input/element-of-set.tf @@ -0,0 +1,7 @@ +resource "test_instance" "a" { + subnet_ids = ["boop"] # this attribute takes a set of strings +} + +output "b" { + value = "${element(test_instance.a.subnet_ids, 0)}" +} diff --git a/configs/configupgrade/test-fixtures/valid/element-of-set/want/element-of-set.tf b/configs/configupgrade/test-fixtures/valid/element-of-set/want/element-of-set.tf new file mode 100644 index 000000000..fcc6c4edf --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/element-of-set/want/element-of-set.tf @@ -0,0 +1,7 @@ +resource "test_instance" "a" { + subnet_ids = ["boop"] # this attribute takes a set of strings +} + +output "b" { + value = element(tolist(test_instance.a.subnet_ids), 0) +} diff --git a/configs/configupgrade/test-fixtures/valid/element-of-set/want/versions.tf b/configs/configupgrade/test-fixtures/valid/element-of-set/want/versions.tf new file mode 100644 index 000000000..d9b6f790b --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/element-of-set/want/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.12" +} diff --git a/configs/configupgrade/upgrade_expr.go b/configs/configupgrade/upgrade_expr.go index b1e40450c..de4480af4 100644 --- a/configs/configupgrade/upgrade_expr.go +++ b/configs/configupgrade/upgrade_expr.go @@ -398,6 +398,23 @@ Value: buf.WriteByte(']') break Value } + case "element": + // We cannot replace element with index syntax safely in general + // because users may be relying on its special modulo wraparound + // behavior that the index syntax doesn't do. However, if it seems + // like the user is trying to use element with a set, we'll insert + // an explicit conversion to list to mimic the implicit conversion + // that we used to do as an unintended side-effect of how functions + // work in HIL. + if len(argExprs) > 0 { + argTy := an.InferExpressionType(argExprs[0], nil) + if argTy.IsSetType() { + newExpr := []byte(`tolist(`) + newExpr = append(newExpr, argExprs[0]...) + newExpr = append(newExpr, ')') + argExprs[0] = newExpr + } + } // HIL used some undocumented special functions to implement certain // operations, but since those were actually callable in real expressions diff --git a/configs/configupgrade/upgrade_test.go b/configs/configupgrade/upgrade_test.go index 92ca69f66..a0c90b21f 100644 --- a/configs/configupgrade/upgrade_test.go +++ b/configs/configupgrade/upgrade_test.go @@ -195,6 +195,7 @@ var testProviders = map[string]providers.Factory{ "image": {Type: cty.String, Optional: true}, "tags": {Type: cty.Map(cty.String), Optional: true}, "security_groups": {Type: cty.List(cty.String), Optional: true}, + "subnet_ids": {Type: cty.Set(cty.String), Optional: true}, }, BlockTypes: map[string]*configschema.NestedBlock{ "network": {