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 }