Skip to content

Commit

Permalink
Fix parsing index field in case of non-numeric string (as allowed i…
Browse files Browse the repository at this point in the history
…n `for_each` construct), fix sorting (#153)

* Fix parsing `index` field in case of non-numeric string (as allowed in `for_each` construct), fix sorting
* Use newer Go versions in CI so functions like `errors.Unwrap` are available
  • Loading branch information
AndiDog authored Jul 30, 2021
1 parent bb01111 commit 8b4b0cc
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 28 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ env:
- GO111MODULE=on

go:
- "1.8"
- "1.11.x"
- "1.13"
- "1.x" # latest

script:
Expand Down
25 changes: 20 additions & 5 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"os"
"sort"
"strings"
)

type counterSorter struct {
Expand All @@ -21,7 +22,7 @@ func (cs counterSorter) Swap(i, j int) {
}

func (cs counterSorter) Less(i, j int) bool {
return cs.resources[i].counter < cs.resources[j].counter
return cs.resources[i].counterNumeric < cs.resources[j].counterNumeric || (cs.resources[i].counterNumeric == cs.resources[j].counterNumeric && cs.resources[i].counterStr < cs.resources[j].counterStr)
}

type allGroup struct {
Expand Down Expand Up @@ -74,8 +75,13 @@ func gatherResourcesPre0dot12(s *state) map[string]interface{} {

unsortedOrdered[res.baseName] = append(unsortedOrdered[res.baseName], res)

// store as invdividual host (eg. <name>_<count>)
invdName := fmt.Sprintf("%s_%d", res.baseName, res.counter)
// store as individual host (e.g. <name>_<count>)
var invdName string
if res.counterStr != "" {
invdName = fmt.Sprintf("%s_%s", res.baseName, strings.Replace(res.counterStr, ".", "_", -1))
} else {
invdName = fmt.Sprintf("%s_%d", res.baseName, res.counterNumeric)
}
if old, exists := individual[invdName]; exists {
fmt.Fprintf(os.Stderr, "overwriting already existing individual key %s, old: %v, new: %v\n", invdName, old, res.Hostname())
}
Expand Down Expand Up @@ -161,11 +167,17 @@ func gatherResources0dot12(s *stateTerraform0dot12) map[string]interface{} {
// place in list of resource types
tp := fmt.Sprintf("type_%s", res.resourceType)
types[tp] = appendUniq(types[tp], res.Hostname())
sort.Strings(types[tp])

unsortedOrdered[res.baseName] = append(unsortedOrdered[res.baseName], res)

// store as invdividual host (eg. <name>_<count>)
invdName := fmt.Sprintf("%s_%d", res.baseName, res.counter)
// store as individual host (e.g. <name>_<count>)
var invdName string
if res.counterStr != "" {
invdName = fmt.Sprintf("%s_%s", res.baseName, strings.Replace(res.counterStr, ".", "_", -1))
} else {
invdName = fmt.Sprintf("%s_%d", res.baseName, res.counterNumeric)
}
if old, exists := individual[invdName]; exists {
fmt.Fprintf(os.Stderr, "overwriting already existing individual key %s, old: %v, new: %v", invdName, old, res.Hostname())
}
Expand All @@ -183,9 +195,12 @@ func gatherResources0dot12(s *stateTerraform0dot12) map[string]interface{} {
tag = resourceIDNames[v]
}
tags[tag] = appendUniq(tags[tag], res.Hostname())
sort.Strings(tags[tag])
}
}

sort.Strings(all.Hosts)

// inventorize outputs as variables
if len(s.outputs()) > 0 {
for _, out := range s.outputs() {
Expand Down
2 changes: 1 addition & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ func (s *stateTerraform0dot12) resources() []*Resource {
case float64:
resourceKeyName += "." + strconv.Itoa(int(v))
case string:
resourceKeyName += "." + v
resourceKeyName += "." + strings.Replace(v, ".", "_", -1)
default:
fmt.Fprintf(os.Stderr, "Warning: unknown index type %v\n", v)
}
Expand Down
67 changes: 63 additions & 4 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,41 @@ const exampleStateFileTerraform0dot12 = `
}
],
"address": "module.my-module-three"
},
{
"resources": [
{
"address": "aws_instance.host",
"type": "aws_instance",
"name": "host",
"index": "for_each_example.first",
"values": {
"ami": "ami-00000000000000001",
"id": "i-44444444444444444",
"private_ip": "10.0.0.4",
"public_ip": "",
"tags": {
"Name": "four-aws-instance"
}
}
},
{
"address": "aws_instance.host",
"type": "aws_instance",
"name": "host",
"index": "for_each_example.second",
"values": {
"ami": "ami-00000000000000001",
"id": "i-11144444444444444",
"private_ip": "10.0.1.4",
"public_ip": "",
"tags": {
"Name": "four-aws-instance"
}
}
}
],
"address": "module.my-module-four"
}
]
}
Expand All @@ -1203,9 +1238,11 @@ const expectedListOutputTerraform0dot12 = `
"hosts": [
"10.0.0.2",
"10.0.0.3",
"10.0.0.4",
"10.0.1.3",
"35.159.25.34",
"12.34.56.78"
"10.0.1.4",
"12.34.56.78",
"35.159.25.34"
],
"vars": {
"my_endpoint": "a.b.c.d.example.com",
Expand All @@ -1215,14 +1252,18 @@ const expectedListOutputTerraform0dot12 = `
},
"one_0": ["35.159.25.34"],
"one": ["35.159.25.34"],
"module_my-module-four_host_for_each_example_first": ["10.0.0.4"],
"module_my-module-four_host_for_each_example_second": ["10.0.1.4"],
"module_my-module-four_host": ["10.0.0.4", "10.0.1.4"],
"module_my-module-two_host_0": ["10.0.0.2"],
"module_my-module-two_host": ["10.0.0.2"],
"module_my-module-three_host_0": ["10.0.0.3"],
"module_my-module-three_host_1": ["10.0.1.3"],
"module_my-module-three_host": ["10.0.0.3", "10.0.1.3"],
"type_aws_instance": ["10.0.0.2", "10.0.0.3", "10.0.1.3", "35.159.25.34"],
"type_aws_instance": ["10.0.0.2", "10.0.0.3", "10.0.0.4", "10.0.1.3", "10.0.1.4", "35.159.25.34"],
"name_four-aws-instance": ["10.0.0.4", "10.0.1.4"],
"name_one-aws-instance": ["35.159.25.34"],
"name_two-aws-instance": ["10.0.0.2"],
"name_three-aws-instance": ["10.0.0.3", "10.0.1.3"],
Expand All @@ -1237,9 +1278,11 @@ const expectedListOutputTerraform0dot12 = `
const expectedInventoryOutputTerraform0dot12 = `[all]
10.0.0.2
10.0.0.3
10.0.0.4
10.0.1.3
35.159.25.34
10.0.1.4
12.34.56.78
35.159.25.34
[all:vars]
map={"first":"a","second":"b"}
Expand All @@ -1249,6 +1292,16 @@ my_password="1234"
[foo_bar]
12.34.56.78
[module_my-module-four_host]
10.0.0.4
10.0.1.4
[module_my-module-four_host_for_each_example_first]
10.0.0.4
[module_my-module-four_host_for_each_example_second]
10.0.1.4
[module_my-module-three_host]
10.0.0.3
10.0.1.3
Expand All @@ -1265,6 +1318,10 @@ my_password="1234"
[module_my-module-two_host_0]
10.0.0.2
[name_four-aws-instance]
10.0.0.4
10.0.1.4
[name_one-aws-instance]
35.159.25.34
Expand All @@ -1284,7 +1341,9 @@ my_password="1234"
[type_aws_instance]
10.0.0.2
10.0.0.3
10.0.0.4
10.0.1.3
10.0.1.4
35.159.25.34
[type_vsphere_virtual_machine]
Expand Down
36 changes: 20 additions & 16 deletions resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ type Resource struct {
// Extracted from keyName
resourceType string
baseName string
counter int

// counterNumeric is 0 for resources created without `count=` attribute or
// having a non-numeric string index
counterNumeric int
// counterStr is set if the resource index (e.g. in `for_each`-constructed
// resources) is not a number.
counterStr string
}

func NewResource(keyName string, state resourceState) (*Resource, error) {
Expand All @@ -72,24 +78,28 @@ func NewResource(keyName string, state resourceState) (*Resource, error) {
return nil, fmt.Errorf("couldn't parse resource keyName: %s", keyName)
}

var c int
counterNumeric := 0
counterStr := ""
var err error
if m[3] != "" {
// The third section should be the index, if it's present. Not sure what
// else we can do other than panic (which seems highly undesirable) if that
// isn't the case. With Terraform 0.12 for_each syntax, index is a string.
c, err = strconv.Atoi(m[3])
// isn't the case. With Terraform 0.12 for_each syntax, index can also be
// a non-numeric string (loop over any string value).
counterNumeric, err = strconv.Atoi(m[3])
if err != nil {
m[2] = fmt.Sprintf("%s.%s", m[2], m[3])
counterNumeric = 0
counterStr = m[3]
}
}

return &Resource{
State: state,
keyName: keyName,
resourceType: m[1],
baseName: m[2],
counter: c,
State: state,
keyName: keyName,
resourceType: m[1],
baseName: m[2],
counterNumeric: counterNumeric,
counterStr: counterStr,
}, nil
}

Expand Down Expand Up @@ -210,12 +220,6 @@ func (r Resource) Attributes() map[string]string {
return r.State.Primary.Attributes
}

// NameWithCounter returns the resource name with its counter. For resources
// created without a 'count=' attribute, this will always be zero.
func (r Resource) NameWithCounter() string {
return fmt.Sprintf("%s.%d", r.baseName, r.counter)
}

// Hostname returns the hostname of this resource.
func (r Resource) Hostname() string {
if keyName := os.Getenv("TF_HOSTNAME_KEY_NAME"); keyName != "" {
Expand Down

0 comments on commit 8b4b0cc

Please sign in to comment.