github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/input_value_test.go (about) 1 package terraform 2 3 import ( 4 "testing" 5 6 "github.com/google/go-cmp/cmp" 7 "github.com/hashicorp/hcl/v2" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 func TestDefaultVariableValues(t *testing.T) { 12 tests := []struct { 13 name string 14 variables map[string]*Variable 15 want InputValues 16 }{ 17 { 18 name: "basic", 19 variables: map[string]*Variable{ 20 "default": {Name: "default", Type: cty.String, Default: cty.StringVal("default")}, 21 "no_default": {Name: "no_default", Type: cty.String}, 22 "null_default": {Name: "null_default", Type: cty.String, Default: cty.NullVal(cty.String)}, 23 }, 24 want: InputValues{ 25 "default": {Value: cty.StringVal("default")}, 26 "no_default": {Value: cty.UnknownVal(cty.String)}, 27 "null_default": {Value: cty.NullVal(cty.String)}, 28 }, 29 }, 30 } 31 32 for _, test := range tests { 33 t.Run(test.name, func(t *testing.T) { 34 got := DefaultVariableValues(test.variables) 35 36 opt := cmp.Comparer(func(x, y cty.Value) bool { 37 return x.RawEquals(y) 38 }) 39 if diff := cmp.Diff(test.want, got, opt); diff != "" { 40 t.Error(diff) 41 } 42 }) 43 } 44 } 45 46 func TestEnvironmentVariableValues(t *testing.T) { 47 neverHappend := func(diags hcl.Diagnostics) bool { return diags.HasErrors() } 48 49 tests := []struct { 50 name string 51 declared map[string]*Variable 52 env map[string]string 53 want InputValues 54 errCheck func(hcl.Diagnostics) bool 55 }{ 56 { 57 name: "undeclared", 58 declared: map[string]*Variable{}, 59 env: map[string]string{ 60 "TF_VAR_instance_type": "t2.micro", 61 "TF_VAR_count": "5", 62 "TF_VAR_list": "[\"foo\"]", 63 "TF_VAR_map": "{foo=\"bar\"}", 64 }, 65 want: InputValues{ 66 "instance_type": &InputValue{ 67 Value: cty.StringVal("t2.micro"), 68 }, 69 "count": &InputValue{ 70 Value: cty.StringVal("5"), 71 }, 72 "list": &InputValue{ 73 Value: cty.StringVal("[\"foo\"]"), 74 }, 75 "map": &InputValue{ 76 Value: cty.StringVal("{foo=\"bar\"}"), 77 }, 78 }, 79 errCheck: neverHappend, 80 }, 81 { 82 name: "declared", 83 declared: map[string]*Variable{ 84 "instance_type": {ParsingMode: VariableParseLiteral}, 85 "count": {ParsingMode: VariableParseHCL}, 86 "list": {ParsingMode: VariableParseHCL}, 87 "map": {ParsingMode: VariableParseHCL}, 88 }, 89 env: map[string]string{ 90 "TF_VAR_instance_type": "t2.micro", 91 "TF_VAR_count": "5", 92 "TF_VAR_list": "[\"foo\"]", 93 "TF_VAR_map": "{foo=\"bar\"}", 94 }, 95 want: InputValues{ 96 "instance_type": &InputValue{ 97 Value: cty.StringVal("t2.micro"), 98 }, 99 "count": &InputValue{ 100 Value: cty.NumberIntVal(5), 101 }, 102 "list": &InputValue{ 103 Value: cty.TupleVal([]cty.Value{cty.StringVal("foo")}), 104 }, 105 "map": &InputValue{ 106 Value: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}), 107 }, 108 }, 109 errCheck: neverHappend, 110 }, 111 { 112 name: "invalid parsing mode", 113 declared: map[string]*Variable{ 114 "foo": {ParsingMode: VariableParseHCL}, 115 }, 116 env: map[string]string{ 117 "TF_VAR_foo": "bar", 118 }, 119 want: InputValues{}, 120 errCheck: func(diags hcl.Diagnostics) bool { 121 return diags.Error() != "<value for var.foo>:1,1-4: Variables not allowed; Variables may not be used here." 122 }, 123 }, 124 { 125 name: "invalid expression", 126 declared: map[string]*Variable{ 127 "foo": {ParsingMode: VariableParseHCL}, 128 }, 129 env: map[string]string{ 130 "TF_VAR_foo": `{"bar": "baz"`, 131 }, 132 want: InputValues{}, 133 errCheck: func(diags hcl.Diagnostics) bool { 134 return diags.Error() != "<value for var.foo>:1,1-2: Unterminated object constructor expression; There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file." 135 }, 136 }, 137 } 138 139 for _, test := range tests { 140 t.Run(test.name, func(t *testing.T) { 141 for k, v := range test.env { 142 t.Setenv(k, v) 143 } 144 145 got, diags := EnvironmentVariableValues(test.declared) 146 if test.errCheck(diags) { 147 t.Fatal(diags) 148 } 149 150 opt := cmp.Comparer(func(x, y cty.Value) bool { 151 return x.RawEquals(y) 152 }) 153 if diff := cmp.Diff(test.want, got, opt); diff != "" { 154 t.Error(diff) 155 } 156 }) 157 } 158 } 159 160 func TestParseVariableValues(t *testing.T) { 161 neverHappend := func(diags hcl.Diagnostics) bool { return diags.HasErrors() } 162 163 tests := []struct { 164 name string 165 declared map[string]*Variable 166 vars []string 167 want InputValues 168 errCheck func(hcl.Diagnostics) bool 169 }{ 170 { 171 name: "undeclared", 172 declared: map[string]*Variable{}, 173 vars: []string{ 174 "foo=bar", 175 }, 176 want: InputValues{}, 177 errCheck: func(diags hcl.Diagnostics) bool { 178 return diags.Error() != `<value for var.foo>:1,1-1: Value for undeclared variable; A variable named "foo" was assigned, but the root module does not declare a variable of that name.` 179 }, 180 }, 181 { 182 name: "declared", 183 declared: map[string]*Variable{ 184 "foo": {ParsingMode: VariableParseLiteral}, 185 "bar": {ParsingMode: VariableParseHCL}, 186 "baz": {ParsingMode: VariableParseHCL}, 187 }, 188 vars: []string{ 189 "foo=bar", 190 "bar=[\"foo\"]", 191 "baz={ foo=\"bar\" }", 192 }, 193 want: InputValues{ 194 "foo": &InputValue{ 195 Value: cty.StringVal("bar"), 196 }, 197 "bar": &InputValue{ 198 Value: cty.TupleVal([]cty.Value{cty.StringVal("foo")}), 199 }, 200 "baz": &InputValue{ 201 Value: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}), 202 }, 203 }, 204 errCheck: neverHappend, 205 }, 206 { 207 name: "invalid format", 208 declared: map[string]*Variable{}, 209 vars: []string{"foo"}, 210 want: InputValues{}, 211 errCheck: func(diags hcl.Diagnostics) bool { 212 return diags.Error() != `<input-value>:1,1-1: invalid variable value format; "foo" is invalid. Variables must be "key=value" format` 213 }, 214 }, 215 { 216 name: "invalid parsing mode", 217 declared: map[string]*Variable{ 218 "foo": {ParsingMode: VariableParseHCL}, 219 }, 220 vars: []string{"foo=bar"}, 221 want: InputValues{}, 222 errCheck: func(diags hcl.Diagnostics) bool { 223 return diags.Error() != "<value for var.foo>:1,1-4: Variables not allowed; Variables may not be used here." 224 }, 225 }, 226 { 227 name: "invalid expression", 228 declared: map[string]*Variable{ 229 "foo": {ParsingMode: VariableParseHCL}, 230 }, 231 vars: []string{"foo="}, 232 want: InputValues{}, 233 errCheck: func(diags hcl.Diagnostics) bool { 234 return diags.Error() != "<value for var.foo>:1,1-1: Missing expression; Expected the start of an expression, but found the end of the file." 235 }, 236 }, 237 } 238 239 for _, test := range tests { 240 t.Run(test.name, func(t *testing.T) { 241 got, diags := ParseVariableValues(test.vars, test.declared) 242 if test.errCheck(diags) { 243 t.Fatal(diags) 244 } 245 246 opt := cmp.Comparer(func(x, y cty.Value) bool { 247 return x.RawEquals(y) 248 }) 249 if diff := cmp.Diff(test.want, got, opt); diff != "" { 250 t.Error(diff) 251 } 252 }) 253 } 254 } 255 256 func TestVariableValues(t *testing.T) { 257 tests := []struct { 258 name string 259 config *Config 260 env map[string]string 261 inputs []InputValues 262 want map[string]map[string]cty.Value 263 }{ 264 { 265 name: "basic", 266 config: &Config{ 267 Path: []string{"child1", "child2"}, 268 Module: &Module{ 269 Variables: map[string]*Variable{ 270 "a": {Name: "a", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")}, 271 "b": {Name: "b", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")}, 272 "c": {Name: "c", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")}, 273 "d": {Name: "d", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")}, 274 }, 275 }, 276 }, 277 env: map[string]string{ 278 "TF_VAR_a": "env", 279 "TF_VAR_b": "env", 280 "TF_VAR_c": "env", 281 }, 282 inputs: []InputValues{ 283 { 284 "a": {Value: cty.StringVal("input1")}, 285 "b": {Value: cty.StringVal("input1")}, 286 }, 287 { 288 "a": {Value: cty.StringVal("input2")}, 289 }, 290 }, 291 want: map[string]map[string]cty.Value{ 292 "module.child1.module.child2": { 293 "a": cty.StringVal("input2"), 294 "b": cty.StringVal("input1"), 295 "c": cty.StringVal("env"), 296 "d": cty.StringVal("config"), 297 }, 298 }, 299 }, 300 } 301 302 for _, test := range tests { 303 t.Run(test.name, func(t *testing.T) { 304 for k, v := range test.env { 305 t.Setenv(k, v) 306 } 307 308 got, diags := VariableValues(test.config, test.inputs...) 309 if diags.HasErrors() { 310 t.Fatal(diags) 311 } 312 313 opt := cmp.Comparer(func(x, y cty.Value) bool { 314 return x.RawEquals(y) 315 }) 316 if diff := cmp.Diff(test.want, got, opt); diff != "" { 317 t.Error(diff) 318 } 319 }) 320 } 321 }