diff --git a/cai2hcl/common/hcl_write.go b/cai2hcl/common/hcl_write.go index 70684f9feb..086f65d27b 100644 --- a/cai2hcl/common/hcl_write.go +++ b/cai2hcl/common/hcl_write.go @@ -2,8 +2,10 @@ package common import ( "fmt" + "sort" + "strings" - "github.com/hashicorp/hcl/hcl/printer" + "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/zclconf/go-cty/cty" ) @@ -13,17 +15,36 @@ func HclWriteBlocks(blocks []*HCLResourceBlock) ([]byte, error) { f := hclwrite.NewFile() rootBody := f.Body() + variableMap := make(map[string]struct{}) + for _, resourceBlock := range blocks { hclBlock := rootBody.AppendNewBlock("resource", resourceBlock.Labels) - if err := hclWriteBlock(resourceBlock.Value, hclBlock.Body()); err != nil { + if err := hclWriteBlock(resourceBlock.Value, hclBlock.Body(), variableMap); err != nil { return nil, err } } - return printer.Format(f.Bytes()) + varNames := make([]string, 0, len(variableMap)) + for name := range variableMap { + varNames = append(varNames, name) + } + sort.Strings(varNames) + + for _, name := range varNames { + rootBody.AppendNewline() + varBlock := rootBody.AppendNewBlock("variable", []string{name}) + + // type = string + t := hcl.Traversal{ + hcl.TraverseRoot{Name: "string"}, + } + varBlock.Body().SetAttributeTraversal("type", t) + } + + return f.Bytes(), nil } -func hclWriteBlock(val cty.Value, body *hclwrite.Body) error { +func hclWriteBlock(val cty.Value, body *hclwrite.Body, variableMap map[string]struct{}) error { if val.IsNull() { return nil } @@ -40,7 +61,7 @@ func hclWriteBlock(val cty.Value, body *hclwrite.Body) error { switch { case objValType.IsObjectType(): newBlock := body.AppendNewBlock(objKey.AsString(), nil) - if err := hclWriteBlock(objVal, newBlock.Body()); err != nil { + if err := hclWriteBlock(objVal, newBlock.Body(), variableMap); err != nil { return err } case objValType.IsCollectionType(): @@ -53,7 +74,7 @@ func hclWriteBlock(val cty.Value, body *hclwrite.Body) error { for listIterator.Next() { _, listVal := listIterator.Element() subBlock := body.AppendNewBlock(objKey.AsString(), nil) - if err := hclWriteBlock(listVal, subBlock.Body()); err != nil { + if err := hclWriteBlock(listVal, subBlock.Body(), variableMap); err != nil { return err } } @@ -61,6 +82,25 @@ func hclWriteBlock(val cty.Value, body *hclwrite.Body) error { } fallthrough default: + if objValType == cty.String { + strVal := objVal.AsString() + if strVal == "" { + continue + } + + if strings.HasPrefix(strVal, "unknown.") { + varName := strings.TrimPrefix(strVal, "unknown.") + variableMap[varName] = struct{}{} + + t := hcl.Traversal{ + hcl.TraverseRoot{Name: "var"}, + hcl.TraverseAttr{Name: varName}, + } + body.SetAttributeTraversal(objKey.AsString(), t) + continue + } + } + if objValType.FriendlyName() == "string" && objVal.AsString() == "" { continue } diff --git a/cai2hcl/common/hcl_write_test.go b/cai2hcl/common/hcl_write_test.go new file mode 100644 index 0000000000..fc15b0854a --- /dev/null +++ b/cai2hcl/common/hcl_write_test.go @@ -0,0 +1,44 @@ +package common + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/zclconf/go-cty/cty" +) + +func TestHclWriteBlocks_VariableReplacement(t *testing.T) { + // Create a test block with a string value starting with "unknown." + blocks := []*HCLResourceBlock{ + { + Labels: []string{"test_resource", "test_name"}, + Value: cty.ObjectVal(map[string]cty.Value{ + "attr1": cty.StringVal("unknown.my_variable"), + "attr2": cty.StringVal("normal_value"), + "attr3": cty.ObjectVal(map[string]cty.Value{ + "nested_attr": cty.StringVal("unknown.another_variable"), + }), + }), + }, + } + + output, err := HclWriteBlocks(blocks) + assert.Nil(t, err) + + hclString := string(output) + + // Check if the variable references are correct + // Note: hclwrite output format might vary slightly, but we expect var.my_variable + // We expect "attr1 = var.my_variable" (no quotes around var.my_variable) + assert.Contains(t, hclString, "attr1 = var.my_variable") + assert.Contains(t, hclString, "nested_attr = var.another_variable") + + // Check if normal strings are still quoted + assert.Contains(t, hclString, `attr2 = "normal_value"`) + + // Check if variable blocks are generated + assert.True(t, strings.Contains(hclString, `variable "my_variable" {`), "missing variable block for my_variable") + assert.True(t, strings.Contains(hclString, `type = string`), "missing type = string") + assert.True(t, strings.Contains(hclString, `variable "another_variable" {`), "missing variable block for another_variable") +}