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  }