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  }