github.com/swisspost/terratest@v0.0.0-20230214120104-7ec6de2e1ae0/modules/terraform/count.go (about) 1 package terraform 2 3 import ( 4 "errors" 5 "regexp" 6 "strconv" 7 8 "github.com/gruntwork-io/terratest/modules/testing" 9 "github.com/stretchr/testify/require" 10 ) 11 12 // ResourceCount represents counts of resources affected by terraform apply/plan/destroy command. 13 type ResourceCount struct { 14 Add int 15 Change int 16 Destroy int 17 } 18 19 // Regular expressions for terraform commands stdout pattern matching. 20 const ( 21 applyRegexp = `Apply complete! Resources: (\d+) added, (\d+) changed, (\d+) destroyed\.` 22 destroyRegexp = `Destroy complete! Resources: (\d+) destroyed\.` 23 planWithChangesRegexp = `(\033\[1m)?Plan:(\033\[0m)? (\d+) to add, (\d+) to change, (\d+) to destroy\.` 24 planWithNoChangesRegexp = `No changes\. (Infrastructure is up-to-date)|(Your infrastructure matches the configuration)\.` 25 26 // '.' doesn't match newline by default in go. We must instruct the regex to match it with the 's' flag. 27 planWithNoInfraChangesRegexp = `(?s)You can apply this plan.+without changing any real infrastructure` 28 ) 29 30 const getResourceCountErrMessage = "Can't parse Terraform output" 31 32 // GetResourceCount parses stdout/stderr of apply/plan/destroy commands and returns number of affected resources. 33 // This will fail the test if given stdout/stderr isn't a valid output of apply/plan/destroy. 34 func GetResourceCount(t testing.TestingT, cmdout string) *ResourceCount { 35 cnt, err := GetResourceCountE(t, cmdout) 36 require.NoError(t, err) 37 return cnt 38 } 39 40 // GetResourceCountE parses stdout/stderr of apply/plan/destroy commands and returns number of affected resources. 41 func GetResourceCountE(t testing.TestingT, cmdout string) (*ResourceCount, error) { 42 cnt := ResourceCount{} 43 44 terraformCommandPatterns := []struct { 45 regexpStr string 46 addPosition int 47 changePosition int 48 destroyPosition int 49 }{ 50 {applyRegexp, 1, 2, 3}, 51 {destroyRegexp, -1, -1, 1}, 52 {planWithChangesRegexp, 3, 4, 5}, 53 {planWithNoChangesRegexp, -1, -1, -1}, 54 {planWithNoInfraChangesRegexp, -1, -1, -1}, 55 } 56 57 for _, tc := range terraformCommandPatterns { 58 pattern, err := regexp.Compile(tc.regexpStr) 59 if err != nil { 60 return nil, err 61 } 62 63 matches := pattern.FindStringSubmatch(cmdout) 64 if matches != nil { 65 if tc.addPosition != -1 { 66 cnt.Add, err = strconv.Atoi(matches[tc.addPosition]) 67 if err != nil { 68 return nil, err 69 } 70 } 71 72 if tc.changePosition != -1 { 73 cnt.Change, err = strconv.Atoi(matches[tc.changePosition]) 74 if err != nil { 75 return nil, err 76 } 77 } 78 79 if tc.destroyPosition != -1 { 80 cnt.Destroy, err = strconv.Atoi(matches[tc.destroyPosition]) 81 if err != nil { 82 return nil, err 83 } 84 } 85 86 return &cnt, nil 87 } 88 } 89 90 return nil, errors.New(getResourceCountErrMessage) 91 }