github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terraform/context_input_test.go (about) 1 package terraform 2 3 import ( 4 "reflect" 5 "strings" 6 "sync" 7 "testing" 8 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/hashicorp/terraform/internal/addrs" 12 "github.com/hashicorp/terraform/internal/configs/configschema" 13 "github.com/hashicorp/terraform/internal/plans" 14 "github.com/hashicorp/terraform/internal/providers" 15 "github.com/hashicorp/terraform/internal/states" 16 ) 17 18 func TestContext2Input_provider(t *testing.T) { 19 m := testModule(t, "input-provider") 20 p := testProvider("aws") 21 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 22 Provider: &configschema.Block{ 23 Attributes: map[string]*configschema.Attribute{ 24 "foo": { 25 Type: cty.String, 26 Required: true, 27 Description: "something something", 28 }, 29 }, 30 }, 31 ResourceTypes: map[string]*configschema.Block{ 32 "aws_instance": { 33 Attributes: map[string]*configschema.Attribute{ 34 "id": { 35 Type: cty.String, 36 Computed: true, 37 }, 38 }, 39 }, 40 }, 41 }) 42 43 inp := &MockUIInput{ 44 InputReturnMap: map[string]string{ 45 "provider.aws.foo": "bar", 46 }, 47 } 48 49 ctx := testContext2(t, &ContextOpts{ 50 Providers: map[addrs.Provider]providers.Factory{ 51 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 52 }, 53 UIInput: inp, 54 }) 55 56 var actual interface{} 57 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 58 actual = req.Config.GetAttr("foo").AsString() 59 return 60 } 61 62 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 63 t.Fatalf("input errors: %s", diags.Err()) 64 } 65 66 if !inp.InputCalled { 67 t.Fatal("no input prompt; want prompt for argument \"foo\"") 68 } 69 if got, want := inp.InputOpts.Description, "something something"; got != want { 70 t.Errorf("wrong description\ngot: %q\nwant: %q", got, want) 71 } 72 73 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 74 assertNoErrors(t, diags) 75 76 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 77 t.Fatalf("apply errors: %s", diags.Err()) 78 } 79 80 if !reflect.DeepEqual(actual, "bar") { 81 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", actual, "bar") 82 } 83 } 84 85 func TestContext2Input_providerMulti(t *testing.T) { 86 m := testModule(t, "input-provider-multi") 87 88 p := testProvider("aws") 89 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 90 Provider: &configschema.Block{ 91 Attributes: map[string]*configschema.Attribute{ 92 "foo": { 93 Type: cty.String, 94 Required: true, 95 Description: "something something", 96 }, 97 }, 98 }, 99 ResourceTypes: map[string]*configschema.Block{ 100 "aws_instance": { 101 Attributes: map[string]*configschema.Attribute{ 102 "id": { 103 Type: cty.String, 104 Computed: true, 105 }, 106 }, 107 }, 108 }, 109 }) 110 111 inp := &MockUIInput{ 112 InputReturnMap: map[string]string{ 113 "provider.aws.foo": "bar", 114 "provider.aws.east.foo": "bar", 115 }, 116 } 117 118 ctx := testContext2(t, &ContextOpts{ 119 Providers: map[addrs.Provider]providers.Factory{ 120 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 121 }, 122 UIInput: inp, 123 }) 124 125 var actual []interface{} 126 var lock sync.Mutex 127 128 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 129 t.Fatalf("input errors: %s", diags.Err()) 130 } 131 132 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 133 assertNoErrors(t, diags) 134 135 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 136 lock.Lock() 137 defer lock.Unlock() 138 actual = append(actual, req.Config.GetAttr("foo").AsString()) 139 return 140 } 141 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 142 t.Fatalf("apply errors: %s", diags.Err()) 143 } 144 145 expected := []interface{}{"bar", "bar"} 146 if !reflect.DeepEqual(actual, expected) { 147 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", actual, expected) 148 } 149 } 150 151 func TestContext2Input_providerOnce(t *testing.T) { 152 m := testModule(t, "input-provider-once") 153 p := testProvider("aws") 154 ctx := testContext2(t, &ContextOpts{ 155 Providers: map[addrs.Provider]providers.Factory{ 156 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 157 }, 158 }) 159 160 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 161 t.Fatalf("input errors: %s", diags.Err()) 162 } 163 } 164 165 func TestContext2Input_providerId(t *testing.T) { 166 input := new(MockUIInput) 167 168 m := testModule(t, "input-provider") 169 170 p := testProvider("aws") 171 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 172 Provider: &configschema.Block{ 173 Attributes: map[string]*configschema.Attribute{ 174 "foo": { 175 Type: cty.String, 176 Required: true, 177 Description: "something something", 178 }, 179 }, 180 }, 181 ResourceTypes: map[string]*configschema.Block{ 182 "aws_instance": { 183 Attributes: map[string]*configschema.Attribute{ 184 "id": { 185 Type: cty.String, 186 Computed: true, 187 }, 188 }, 189 }, 190 }, 191 }) 192 193 ctx := testContext2(t, &ContextOpts{ 194 Providers: map[addrs.Provider]providers.Factory{ 195 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 196 }, 197 UIInput: input, 198 }) 199 200 var actual interface{} 201 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 202 actual = req.Config.GetAttr("foo").AsString() 203 return 204 } 205 206 input.InputReturnMap = map[string]string{ 207 "provider.aws.foo": "bar", 208 } 209 210 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 211 t.Fatalf("input errors: %s", diags.Err()) 212 } 213 214 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 215 assertNoErrors(t, diags) 216 217 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 218 t.Fatalf("apply errors: %s", diags.Err()) 219 } 220 221 if !reflect.DeepEqual(actual, "bar") { 222 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", actual, "bar") 223 } 224 } 225 226 func TestContext2Input_providerOnly(t *testing.T) { 227 input := new(MockUIInput) 228 229 m := testModule(t, "input-provider-vars") 230 p := testProvider("aws") 231 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 232 Provider: &configschema.Block{ 233 Attributes: map[string]*configschema.Attribute{ 234 "foo": { 235 Type: cty.String, 236 Required: true, 237 }, 238 }, 239 }, 240 ResourceTypes: map[string]*configschema.Block{ 241 "aws_instance": { 242 Attributes: map[string]*configschema.Attribute{ 243 "foo": {Type: cty.String, Required: true}, 244 "id": {Type: cty.String, Computed: true}, 245 "type": {Type: cty.String, Computed: true}, 246 }, 247 }, 248 }, 249 }) 250 251 ctx := testContext2(t, &ContextOpts{ 252 Providers: map[addrs.Provider]providers.Factory{ 253 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 254 }, 255 UIInput: input, 256 }) 257 258 input.InputReturnMap = map[string]string{ 259 "provider.aws.foo": "bar", 260 } 261 262 var actual interface{} 263 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 264 actual = req.Config.GetAttr("foo").AsString() 265 return 266 } 267 268 if err := ctx.Input(m, InputModeProvider); err != nil { 269 t.Fatalf("err: %s", err) 270 } 271 272 // NOTE: This is a stale test case from an older version of Terraform 273 // where Input was responsible for prompting for both input variables _and_ 274 // provider configuration arguments, where it was trying to test the case 275 // where we were turning off the mode of prompting for input variables. 276 // That's now always disabled, and so this is essentially the same as the 277 // normal Input test, but we're preserving it until we have time to review 278 // and make sure this isn't inadvertently providing unique test coverage 279 // other than what it set out to test. 280 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 281 Mode: plans.NormalMode, 282 SetVariables: InputValues{ 283 "foo": &InputValue{ 284 Value: cty.StringVal("us-west-2"), 285 SourceType: ValueFromCaller, 286 }, 287 }, 288 }) 289 assertNoErrors(t, diags) 290 291 state, err := ctx.Apply(plan, m) 292 if err != nil { 293 t.Fatalf("err: %s", err) 294 } 295 296 if !reflect.DeepEqual(actual, "bar") { 297 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", actual, "bar") 298 } 299 300 actualStr := strings.TrimSpace(state.String()) 301 expectedStr := strings.TrimSpace(testTerraformInputProviderOnlyStr) 302 if actualStr != expectedStr { 303 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actualStr, expectedStr) 304 } 305 } 306 307 func TestContext2Input_providerVars(t *testing.T) { 308 input := new(MockUIInput) 309 m := testModule(t, "input-provider-with-vars") 310 p := testProvider("aws") 311 ctx := testContext2(t, &ContextOpts{ 312 Providers: map[addrs.Provider]providers.Factory{ 313 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 314 }, 315 UIInput: input, 316 }) 317 318 input.InputReturnMap = map[string]string{ 319 "var.foo": "bar", 320 } 321 322 var actual interface{} 323 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 324 actual = req.Config.GetAttr("foo").AsString() 325 return 326 } 327 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 328 t.Fatalf("input errors: %s", diags.Err()) 329 } 330 331 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 332 Mode: plans.NormalMode, 333 SetVariables: InputValues{ 334 "foo": &InputValue{ 335 Value: cty.StringVal("bar"), 336 SourceType: ValueFromCaller, 337 }, 338 }, 339 }) 340 assertNoErrors(t, diags) 341 342 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 343 t.Fatalf("apply errors: %s", diags.Err()) 344 } 345 346 if !reflect.DeepEqual(actual, "bar") { 347 t.Fatalf("bad: %#v", actual) 348 } 349 } 350 351 func TestContext2Input_providerVarsModuleInherit(t *testing.T) { 352 input := new(MockUIInput) 353 m := testModule(t, "input-provider-with-vars-and-module") 354 p := testProvider("aws") 355 ctx := testContext2(t, &ContextOpts{ 356 Providers: map[addrs.Provider]providers.Factory{ 357 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 358 }, 359 UIInput: input, 360 }) 361 362 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 363 t.Fatalf("input errors: %s", diags.Err()) 364 } 365 } 366 367 // adding a list interpolation in fails to interpolate the count variable 368 func TestContext2Input_submoduleTriggersInvalidCount(t *testing.T) { 369 input := new(MockUIInput) 370 m := testModule(t, "input-submodule-count") 371 p := testProvider("aws") 372 ctx := testContext2(t, &ContextOpts{ 373 Providers: map[addrs.Provider]providers.Factory{ 374 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 375 }, 376 UIInput: input, 377 }) 378 379 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 380 t.Fatalf("input errors: %s", diags.Err()) 381 } 382 } 383 384 // In this case, a module variable can't be resolved from a data source until 385 // it's refreshed, but it can't be refreshed during Input. 386 func TestContext2Input_dataSourceRequiresRefresh(t *testing.T) { 387 input := new(MockUIInput) 388 p := testProvider("null") 389 m := testModule(t, "input-module-data-vars") 390 391 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 392 DataSources: map[string]*configschema.Block{ 393 "null_data_source": { 394 Attributes: map[string]*configschema.Attribute{ 395 "foo": {Type: cty.List(cty.String), Optional: true}, 396 }, 397 }, 398 }, 399 }) 400 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 401 return providers.ReadDataSourceResponse{ 402 State: req.Config, 403 } 404 } 405 406 state := states.BuildState(func(s *states.SyncState) { 407 s.SetResourceInstanceCurrent( 408 addrs.Resource{ 409 Mode: addrs.DataResourceMode, 410 Type: "null_data_source", 411 Name: "bar", 412 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 413 &states.ResourceInstanceObjectSrc{ 414 AttrsFlat: map[string]string{ 415 "id": "-", 416 "foo.#": "1", 417 "foo.0": "a", 418 // foo.1 exists in the data source, but needs to be refreshed. 419 }, 420 Status: states.ObjectReady, 421 }, 422 addrs.AbsProviderConfig{ 423 Provider: addrs.NewDefaultProvider("null"), 424 Module: addrs.RootModule, 425 }, 426 ) 427 }) 428 429 ctx := testContext2(t, &ContextOpts{ 430 Providers: map[addrs.Provider]providers.Factory{ 431 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 432 }, 433 UIInput: input, 434 }) 435 436 if diags := ctx.Input(m, InputModeStd); diags.HasErrors() { 437 t.Fatalf("input errors: %s", diags.Err()) 438 } 439 440 // ensure that plan works after Refresh. This is a legacy test that 441 // doesn't really make sense anymore, because Refresh is really just 442 // a wrapper around plan anyway, but we're keeping it until we get a 443 // chance to review and check whether it's giving us any additional 444 // test coverage aside from what it's specifically intending to test. 445 if _, diags := ctx.Refresh(m, state, DefaultPlanOpts); diags.HasErrors() { 446 t.Fatalf("refresh errors: %s", diags.Err()) 447 } 448 if _, diags := ctx.Plan(m, state, DefaultPlanOpts); diags.HasErrors() { 449 t.Fatalf("plan errors: %s", diags.Err()) 450 } 451 }