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 }