github.com/darmach/terratest@v0.34.8-0.20210517103231-80931f95e3ff/modules/terraform/output.go (about)

     1  package terraform
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strconv"
     9  
    10  	"github.com/gruntwork-io/terratest/modules/testing"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  // Output calls terraform output for the given variable and return its string value representation.
    15  // It only designed to work with primitive terraform types: string, number and bool.
    16  // Please use OutputStruct for anything else.
    17  func Output(t testing.TestingT, options *Options, key string) string {
    18  	out, err := OutputE(t, options, key)
    19  	require.NoError(t, err)
    20  	return out
    21  }
    22  
    23  // OutputE calls terraform output for the given variable and return its string value representation.
    24  // It only designed to work with primitive terraform types: string, number and bool.
    25  // Please use OutputStructE for anything else.
    26  func OutputE(t testing.TestingT, options *Options, key string) (string, error) {
    27  	var val interface{}
    28  	err := OutputStructE(t, options, key, &val)
    29  	return fmt.Sprintf("%v", val), err
    30  }
    31  
    32  // OutputRequired calls terraform output for the given variable and return its value. If the value is empty, fail the test.
    33  func OutputRequired(t testing.TestingT, options *Options, key string) string {
    34  	out, err := OutputRequiredE(t, options, key)
    35  	require.NoError(t, err)
    36  	return out
    37  }
    38  
    39  // OutputRequiredE calls terraform output for the given variable and return its value. If the value is empty, return an error.
    40  func OutputRequiredE(t testing.TestingT, options *Options, key string) (string, error) {
    41  	out, err := OutputE(t, options, key)
    42  
    43  	if err != nil {
    44  		return "", err
    45  	}
    46  	if out == "" {
    47  		return "", EmptyOutput(key)
    48  	}
    49  
    50  	return out, nil
    51  }
    52  
    53  // parseListOfMaps takes a list of maps and parses the types.
    54  // It is mainly a wrapper for parseMap to support lists.
    55  func parseListOfMaps(l []interface{}) ([]map[string]interface{}, error) {
    56  	var result []map[string]interface{}
    57  
    58  	for _, v := range l {
    59  
    60  		asMap, isMap := v.(map[string]interface{})
    61  		if !isMap {
    62  			err := errors.New("Type switching to map[string]interface{} failed.")
    63  			return nil, err
    64  		}
    65  
    66  		m, err := parseMap(asMap)
    67  
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		result = append(result, m)
    72  	}
    73  
    74  	return result, nil
    75  
    76  }
    77  
    78  // parseMap takes a map of interfaces and parses the types.
    79  // It is recursive which allows it to support complex nested structures.
    80  // At this time, this function uses https://golang.org/pkg/strconv/#ParseInt
    81  // to determine if a number should be a float or an int. For this reason, if you are
    82  // expecting a float with a zero as the "tenth" you will need to manually convert
    83  // the return value to a float.
    84  //
    85  // This function exists to map return values of the terraform outputs to intuitive
    86  // types. ie, if you are expecting a value of "1" you are implicitly expecting an int.
    87  //
    88  // This also allows the work to be executed recursively to support complex data types.
    89  func parseMap(m map[string]interface{}) (map[string]interface{}, error) {
    90  
    91  	result := make(map[string]interface{})
    92  
    93  	for k, v := range m {
    94  		switch vt := v.(type) {
    95  		case map[string]interface{}:
    96  			nestedMap, err := parseMap(vt)
    97  			if err != nil {
    98  				return nil, err
    99  			}
   100  			result[k] = nestedMap
   101  		case []interface{}:
   102  			nestedList, err := parseListOfMaps(vt)
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  			result[k] = nestedList
   107  		case float64:
   108  			testInt, err := strconv.ParseInt((fmt.Sprintf("%v", vt)), 10, 0)
   109  			if err == nil {
   110  				result[k] = int(testInt)
   111  			} else {
   112  				result[k] = vt
   113  			}
   114  		default:
   115  			result[k] = vt
   116  		}
   117  
   118  	}
   119  
   120  	return result, nil
   121  }
   122  
   123  // OutputMapOfObjects calls terraform output for the given variable and returns its value as a map of lists/maps.
   124  // If the output value is not a map of lists/maps, then it fails the test.
   125  func OutputMapOfObjects(t testing.TestingT, options *Options, key string) map[string]interface{} {
   126  	out, err := OutputMapOfObjectsE(t, options, key)
   127  	require.NoError(t, err)
   128  	return out
   129  }
   130  
   131  // OutputMapOfObjectsE calls terraform output for the given variable and returns its value as a map of lists/maps.
   132  // Also returns an error object if an error was generated.
   133  // If the output value is not a map of lists/maps, then it fails the test.
   134  func OutputMapOfObjectsE(t testing.TestingT, options *Options, key string) (map[string]interface{}, error) {
   135  	out, err := OutputJsonE(t, options, key)
   136  
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	var output map[string]interface{}
   142  
   143  	if err := json.Unmarshal([]byte(out), &output); err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	return parseMap(output)
   148  }
   149  
   150  // OutputListOfObjects calls terraform output for the given variable and returns its value as a list of maps/lists.
   151  // If the output value is not a list of maps/lists, then it fails the test.
   152  func OutputListOfObjects(t testing.TestingT, options *Options, key string) []map[string]interface{} {
   153  	out, err := OutputListOfObjectsE(t, options, key)
   154  	require.NoError(t, err)
   155  	return out
   156  }
   157  
   158  // OutputListOfObjectsE calls terraform output for the given variable and returns its value as a list of maps/lists.
   159  // Also returns an error object if an error was generated.
   160  // If the output value is not a list of maps/lists, then it fails the test.
   161  func OutputListOfObjectsE(t testing.TestingT, options *Options, key string) ([]map[string]interface{}, error) {
   162  	out, err := OutputJsonE(t, options, key)
   163  
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	var output []map[string]interface{}
   169  
   170  	if err := json.Unmarshal([]byte(out), &output); err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	var result []map[string]interface{}
   175  
   176  	for _, m := range output {
   177  		newMap, err := parseMap(m)
   178  
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  
   183  		result = append(result, newMap)
   184  	}
   185  
   186  	return result, nil
   187  }
   188  
   189  // OutputList calls terraform output for the given variable and returns its value as a list.
   190  // If the output value is not a list type, then it fails the test.
   191  func OutputList(t testing.TestingT, options *Options, key string) []string {
   192  	out, err := OutputListE(t, options, key)
   193  	require.NoError(t, err)
   194  	return out
   195  }
   196  
   197  // OutputListE calls terraform output for the given variable and returns its value as a list.
   198  // If the output value is not a list type, then it returns an error.
   199  func OutputListE(t testing.TestingT, options *Options, key string) ([]string, error) {
   200  	out, err := OutputJsonE(t, options, key)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	var output interface{}
   206  	if err := json.Unmarshal([]byte(out), &output); err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	if outputList, isList := output.([]interface{}); isList {
   211  		return parseListOutputTerraform(outputList, key)
   212  	}
   213  
   214  	return nil, UnexpectedOutputType{Key: key, ExpectedType: "map or list", ActualType: reflect.TypeOf(output).String()}
   215  }
   216  
   217  // Parse a list output in the format it is returned by Terraform 0.12 and newer versions
   218  func parseListOutputTerraform(outputList []interface{}, key string) ([]string, error) {
   219  	list := []string{}
   220  
   221  	for _, item := range outputList {
   222  		list = append(list, fmt.Sprintf("%v", item))
   223  	}
   224  
   225  	return list, nil
   226  }
   227  
   228  // OutputMap calls terraform output for the given variable and returns its value as a map.
   229  // If the output value is not a map type, then it fails the test.
   230  func OutputMap(t testing.TestingT, options *Options, key string) map[string]string {
   231  	out, err := OutputMapE(t, options, key)
   232  	require.NoError(t, err)
   233  	return out
   234  }
   235  
   236  // OutputMapE calls terraform output for the given variable and returns its value as a map.
   237  // If the output value is not a map type, then it returns an error.
   238  func OutputMapE(t testing.TestingT, options *Options, key string) (map[string]string, error) {
   239  	out, err := OutputJsonE(t, options, key)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	outputMap := map[string]interface{}{}
   245  	if err := json.Unmarshal([]byte(out), &outputMap); err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	resultMap := make(map[string]string)
   250  	for k, v := range outputMap {
   251  		resultMap[k] = fmt.Sprintf("%v", v)
   252  	}
   253  	return resultMap, nil
   254  }
   255  
   256  // OutputForKeys calls terraform output for the given key list and returns values as a map.
   257  // If keys not found in the output, fails the test
   258  func OutputForKeys(t testing.TestingT, options *Options, keys []string) map[string]interface{} {
   259  	out, err := OutputForKeysE(t, options, keys)
   260  	require.NoError(t, err)
   261  	return out
   262  }
   263  
   264  // OutputJson calls terraform output for the given variable and returns the
   265  // result as the json string.
   266  // If key is an empty string, it will return all the output variables.
   267  func OutputJson(t testing.TestingT, options *Options, key string) string {
   268  	str, err := OutputJsonE(t, options, key)
   269  	require.NoError(t, err)
   270  	return str
   271  }
   272  
   273  // OutputJsonE calls terraform output for the given variable and returns the
   274  // result as the json string.
   275  // If key is an empty string, it will return all the output variables.
   276  func OutputJsonE(t testing.TestingT, options *Options, key string) (string, error) {
   277  	args := []string{"output", "-no-color", "-json"}
   278  	if key != "" {
   279  		args = append(args, key)
   280  	}
   281  
   282  	return RunTerraformCommandAndGetStdoutE(t, options, args...)
   283  }
   284  
   285  // OutputStruct calls terraform output for the given variable and stores the
   286  // result in the value pointed to by v. If v is nil or not a pointer, or if
   287  // the value returned by Terraform is not appropriate for a given target type,
   288  // it fails the test.
   289  func OutputStruct(t testing.TestingT, options *Options, key string, v interface{}) {
   290  	err := OutputStructE(t, options, key, v)
   291  	require.NoError(t, err)
   292  }
   293  
   294  // OutputStructE calls terraform output for the given variable and stores the
   295  // result in the value pointed to by v. If v is nil or not a pointer, or if
   296  // the value returned by Terraform is not appropriate for a given target type,
   297  // it returns an error.
   298  func OutputStructE(t testing.TestingT, options *Options, key string, v interface{}) error {
   299  	out, err := OutputJsonE(t, options, key)
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	return json.Unmarshal([]byte(out), &v)
   305  }
   306  
   307  // OutputForKeysE calls terraform output for the given key list and returns values as a map.
   308  // The returned values are of type interface{} and need to be type casted as necessary. Refer to output_test.go
   309  func OutputForKeysE(t testing.TestingT, options *Options, keys []string) (map[string]interface{}, error) {
   310  	out, err := OutputJsonE(t, options, "")
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	outputMap := map[string]map[string]interface{}{}
   316  	if err := json.Unmarshal([]byte(out), &outputMap); err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	if keys == nil {
   321  		outputKeys := make([]string, 0, len(outputMap))
   322  		for k := range outputMap {
   323  			outputKeys = append(outputKeys, k)
   324  		}
   325  		keys = outputKeys
   326  	}
   327  
   328  	resultMap := make(map[string]interface{})
   329  	for _, key := range keys {
   330  		value, containsValue := outputMap[key]["value"]
   331  		if !containsValue {
   332  			return nil, OutputKeyNotFound(string(key))
   333  		}
   334  		resultMap[key] = value
   335  	}
   336  	return resultMap, nil
   337  }
   338  
   339  // OutputAll calls terraform output returns all values as a map.
   340  // If there is error fetching the output, fails the test
   341  func OutputAll(t testing.TestingT, options *Options) map[string]interface{} {
   342  	out, err := OutputAllE(t, options)
   343  	require.NoError(t, err)
   344  	return out
   345  }
   346  
   347  // OutputAllE calls terraform and returns all the outputs as a map
   348  func OutputAllE(t testing.TestingT, options *Options) (map[string]interface{}, error) {
   349  	return OutputForKeysE(t, options, nil)
   350  }