github.com/terraform-modules-krish/terratest@v0.29.0/modules/terraform/output.go (about)

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