Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test that map keys don't get renamed #101

Merged
merged 1 commit into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- Support the `replace` function.

- Don't rename map keys.

### Bug Fixes

- Fix order of parameters for the `parseint` function
17 changes: 17 additions & 0 deletions pkg/convert/testdata/programs/map_keys/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
resource "complex_resource" "a_resource" {
a_map_of_bool = {
CAPS: true
camelCase: false
snake_case: false
PascalCase: true
}
}

output "some_map_output" {
value = {
CAPS: complex_resource.a_resource.result
camelCase: 1
snake_case: 2
PascalCase: 3
}
}
18 changes: 18 additions & 0 deletions pkg/convert/testdata/programs/map_keys/pcl/main.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
resource "aResource" "complex:index/index:resource" {
__logicalName = "a_resource"
aMapOfBool = {
CAPS = true
camelCase = false
snake_case = false
PascalCase = true
}
}

output "someMapOutput" {
value = {
CAPS = aResource.result
camelCase = 1
snakeCase = 2
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these are still being renamed. Fixing this is a bit more involved. If we just flip the logic to assume things are maps not objects then we end up renaming a lot of { k: v} expressions in TF source code which are then used for object values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably fine that output objects are renamed because the type of these becomes are object type from component outputs, not a map.

pascalCase = 3
}
}
42 changes: 26 additions & 16 deletions pkg/convert/tf.go
Original file line number Diff line number Diff line change
Expand Up @@ -914,38 +914,38 @@ func appendPathArray(root string) string {
return root + "[]"
}

// matchStaticString returns a literal string if the expression is a static string or identifier, else nil
func matchStaticString(expr hclsyntax.Expression) *string {
// matchStaticString returns a literal string if the expression is a static string or identifier, else nil. It returns true if this was an identifier.
func matchStaticString(expr hclsyntax.Expression) (*string, bool) {
switch expr := expr.(type) {
case *hclsyntax.ObjectConsKeyExpr:
return matchStaticString(expr.Wrapped)
case *hclsyntax.LiteralValueExpr:
if expr.Val.Type() != cty.String {
return nil
return nil, false
}
s := expr.Val.AsString()
return &s
return &s, false
case *hclsyntax.ScopeTraversalExpr:
if len(expr.Traversal) != 1 {
return nil
return nil, false
}
if root, ok := expr.Traversal[0].(hcl.TraverseRoot); ok {
s := root.Name
return &s
return &s, true
}
case *hclsyntax.TemplateExpr:
if len(expr.Parts) != 1 {
return nil
return nil, false
}
if lit, ok := expr.Parts[0].(*hclsyntax.LiteralValueExpr); ok {
if lit.Val.Type() != cty.String {
return nil
return nil, false
}
s := lit.Val.AsString()
return &s
return &s, false
}
}
return nil
return nil, false
}

func convertObjectConsExpr(state *convertState, inBlock bool, scopes *scopes,
Expand All @@ -963,12 +963,22 @@ func convertObjectConsExpr(state *convertState, inBlock bool, scopes *scopes,
// object if it's identifiers. Currently we just default to assuming it's an object.
isMap := scopes.isMap(fullyQualifiedPath)

if isMap != nil && !*isMap {
// We know this isn't a map type, so we should try to rename the keys
name := matchStaticString(item.KeyExpr)
if name != nil {
name, isIdentifier := matchStaticString(item.KeyExpr)
if name != nil {
if isMap != nil && *isMap {
// We know this _is_ a map type, so we should leave the keys alone. We can only do this for static
// strings other expressions are just going to have to be converted.
} else {
// We either don't know this type, or know it's not a map, so we should try to rename the keys.
subQualifiedPath = appendPath(fullyQualifiedPath, *name)
nameTokens = hclwrite.TokensForIdentifier(scopes.pulumiName(subQualifiedPath))
*name = scopes.pulumiName(subQualifiedPath)
}

// If this was a literal string (i.e. not an identifier) then make sure we still quote it.
if !isIdentifier {
nameTokens = hclwrite.TokensForValue(cty.StringVal(*name))
} else {
nameTokens = hclwrite.TokensForIdentifier(*name)
}
}
}
Expand Down Expand Up @@ -1697,7 +1707,7 @@ func convertBody(state *convertState, scopes *scopes, fullyQualifiedPath string,
var tfEachVar string
iteratorAttr, has := dynamicBody.Attributes["iterator"]
if has {
str := matchStaticString(iteratorAttr.Expr)
str, _ := matchStaticString(iteratorAttr.Expr)
if str == nil {
panic("iterator must be a static string")
}
Expand Down
Loading