From 8b4b0ccdc08a20205e9622f6749c9450f585c96e Mon Sep 17 00:00:00 2001 From: Andreas Sommer Date: Fri, 30 Jul 2021 23:06:38 +0200 Subject: [PATCH] Fix parsing `index` field in case of non-numeric string (as allowed in `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 --- .travis.yml | 3 +-- cli.go | 25 +++++++++++++++---- parser.go | 2 +- parser_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++--- resource.go | 36 +++++++++++++++------------ 5 files changed, 105 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3e9cbf..195681b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ env: - GO111MODULE=on go: - - "1.8" - - "1.11.x" + - "1.13" - "1.x" # latest script: diff --git a/cli.go b/cli.go index fb9e58f..83a79a4 100644 --- a/cli.go +++ b/cli.go @@ -6,6 +6,7 @@ import ( "io" "os" "sort" + "strings" ) type counterSorter struct { @@ -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 { @@ -74,8 +75,13 @@ func gatherResourcesPre0dot12(s *state) map[string]interface{} { unsortedOrdered[res.baseName] = append(unsortedOrdered[res.baseName], res) - // store as invdividual host (eg. _) - invdName := fmt.Sprintf("%s_%d", res.baseName, res.counter) + // store as individual host (e.g. _) + 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()) } @@ -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. _) - invdName := fmt.Sprintf("%s_%d", res.baseName, res.counter) + // store as individual host (e.g. _) + 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()) } @@ -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() { diff --git a/parser.go b/parser.go index d2bffe9..3e0be3b 100644 --- a/parser.go +++ b/parser.go @@ -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) } diff --git a/parser_test.go b/parser_test.go index 2089b82..c6854b5 100644 --- a/parser_test.go +++ b/parser_test.go @@ -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" } ] } @@ -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", @@ -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"], @@ -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"} @@ -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 @@ -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 @@ -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] diff --git a/resource.go b/resource.go index 6af7724..0fd0a59 100644 --- a/resource.go +++ b/resource.go @@ -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) { @@ -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 } @@ -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 != "" {