github.com/LorbusChris/terraform@v0.11.12-beta1/terraform/interpolate_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/davecgh/go-spew/spew"
    11  	"github.com/hashicorp/hil"
    12  	"github.com/hashicorp/hil/ast"
    13  	"github.com/hashicorp/terraform/config"
    14  )
    15  
    16  func TestInterpolater_simpleVar(t *testing.T) {
    17  	i := &Interpolater{}
    18  	scope := &InterpolationScope{}
    19  	testInterpolateErr(t, i, scope, "simple")
    20  }
    21  
    22  func TestInterpolater_countIndex(t *testing.T) {
    23  	i := &Interpolater{}
    24  
    25  	scope := &InterpolationScope{
    26  		Path:     rootModulePath,
    27  		Resource: &Resource{CountIndex: 42},
    28  	}
    29  
    30  	testInterpolate(t, i, scope, "count.index", ast.Variable{
    31  		Value: 42,
    32  		Type:  ast.TypeInt,
    33  	})
    34  }
    35  
    36  func TestInterpolater_countIndexInWrongContext(t *testing.T) {
    37  	i := &Interpolater{}
    38  
    39  	scope := &InterpolationScope{
    40  		Path: rootModulePath,
    41  	}
    42  
    43  	n := "count.index"
    44  
    45  	v, err := config.NewInterpolatedVariable(n)
    46  	if err != nil {
    47  		t.Fatalf("err: %s", err)
    48  	}
    49  
    50  	expectedErr := fmt.Errorf("foo: count.index is only valid within resources")
    51  
    52  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
    53  		"foo": v,
    54  	})
    55  
    56  	if !reflect.DeepEqual(expectedErr, err) {
    57  		t.Fatalf("expected: %#v, got %#v", expectedErr, err)
    58  	}
    59  }
    60  
    61  func TestInterpolater_moduleVariable(t *testing.T) {
    62  	lock := new(sync.RWMutex)
    63  	state := &State{
    64  		Modules: []*ModuleState{
    65  			&ModuleState{
    66  				Path: rootModulePath,
    67  				Resources: map[string]*ResourceState{
    68  					"aws_instance.web": &ResourceState{
    69  						Type: "aws_instance",
    70  						Primary: &InstanceState{
    71  							ID: "bar",
    72  						},
    73  					},
    74  				},
    75  			},
    76  			&ModuleState{
    77  				Path: []string{RootModuleName, "child"},
    78  				Outputs: map[string]*OutputState{
    79  					"foo": &OutputState{
    80  						Type:  "string",
    81  						Value: "bar",
    82  					},
    83  				},
    84  			},
    85  		},
    86  	}
    87  
    88  	i := &Interpolater{
    89  		State:     state,
    90  		StateLock: lock,
    91  	}
    92  
    93  	scope := &InterpolationScope{
    94  		Path: rootModulePath,
    95  	}
    96  
    97  	testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
    98  		Value: "bar",
    99  		Type:  ast.TypeString,
   100  	})
   101  }
   102  
   103  func TestInterpolater_localVal(t *testing.T) {
   104  	lock := new(sync.RWMutex)
   105  	state := &State{
   106  		Modules: []*ModuleState{
   107  			&ModuleState{
   108  				Path: rootModulePath,
   109  				Locals: map[string]interface{}{
   110  					"foo": "hello!",
   111  				},
   112  			},
   113  		},
   114  	}
   115  
   116  	i := &Interpolater{
   117  		Module:    testModule(t, "interpolate-local"),
   118  		State:     state,
   119  		StateLock: lock,
   120  	}
   121  
   122  	scope := &InterpolationScope{
   123  		Path: rootModulePath,
   124  	}
   125  
   126  	testInterpolate(t, i, scope, "local.foo", ast.Variable{
   127  		Value: "hello!",
   128  		Type:  ast.TypeString,
   129  	})
   130  }
   131  
   132  func TestInterpolater_missingID(t *testing.T) {
   133  	lock := new(sync.RWMutex)
   134  	state := &State{
   135  		Modules: []*ModuleState{
   136  			&ModuleState{
   137  				Path: rootModulePath,
   138  				Resources: map[string]*ResourceState{
   139  					"aws_instance.web": &ResourceState{
   140  						Type: "aws_instance",
   141  						Primary: &InstanceState{
   142  							ID: "bar",
   143  						},
   144  					},
   145  				},
   146  			},
   147  		},
   148  	}
   149  
   150  	i := &Interpolater{
   151  		Module:    testModule(t, "interpolate-resource-variable"),
   152  		State:     state,
   153  		StateLock: lock,
   154  	}
   155  
   156  	scope := &InterpolationScope{
   157  		Path: rootModulePath,
   158  	}
   159  
   160  	testInterpolate(t, i, scope, "aws_instance.web.id", ast.Variable{
   161  		Value: "bar",
   162  		Type:  ast.TypeString,
   163  	})
   164  }
   165  
   166  func TestInterpolater_pathCwd(t *testing.T) {
   167  	i := &Interpolater{}
   168  	scope := &InterpolationScope{}
   169  
   170  	expected, err := os.Getwd()
   171  	if err != nil {
   172  		t.Fatalf("err: %s", err)
   173  	}
   174  
   175  	testInterpolate(t, i, scope, "path.cwd", ast.Variable{
   176  		Value: expected,
   177  		Type:  ast.TypeString,
   178  	})
   179  }
   180  
   181  func TestInterpolater_pathModule(t *testing.T) {
   182  	mod := testModule(t, "interpolate-path-module")
   183  	i := &Interpolater{
   184  		Module: mod,
   185  	}
   186  	scope := &InterpolationScope{
   187  		Path: []string{RootModuleName, "child"},
   188  	}
   189  
   190  	path := mod.Child([]string{"child"}).Config().Dir
   191  	testInterpolate(t, i, scope, "path.module", ast.Variable{
   192  		Value: path,
   193  		Type:  ast.TypeString,
   194  	})
   195  }
   196  
   197  func TestInterpolater_pathRoot(t *testing.T) {
   198  	mod := testModule(t, "interpolate-path-module")
   199  	i := &Interpolater{
   200  		Module: mod,
   201  	}
   202  	scope := &InterpolationScope{
   203  		Path: []string{RootModuleName, "child"},
   204  	}
   205  
   206  	path := mod.Config().Dir
   207  	testInterpolate(t, i, scope, "path.root", ast.Variable{
   208  		Value: path,
   209  		Type:  ast.TypeString,
   210  	})
   211  }
   212  
   213  func TestInterpolater_resourceVariableMap(t *testing.T) {
   214  	lock := new(sync.RWMutex)
   215  	state := &State{
   216  		Modules: []*ModuleState{
   217  			&ModuleState{
   218  				Path: rootModulePath,
   219  				Resources: map[string]*ResourceState{
   220  					"aws_instance.web": &ResourceState{
   221  						Type: "aws_instance",
   222  						Primary: &InstanceState{
   223  							ID: "bar",
   224  							Attributes: map[string]string{
   225  								"amap.%":    "3",
   226  								"amap.key1": "value1",
   227  								"amap.key2": "value2",
   228  								"amap.key3": "value3",
   229  							},
   230  						},
   231  					},
   232  				},
   233  			},
   234  		},
   235  	}
   236  
   237  	i := &Interpolater{
   238  		Module:    testModule(t, "interpolate-resource-variable"),
   239  		State:     state,
   240  		StateLock: lock,
   241  	}
   242  
   243  	scope := &InterpolationScope{
   244  		Path: rootModulePath,
   245  	}
   246  
   247  	expected := map[string]interface{}{
   248  		"key1": "value1",
   249  		"key2": "value2",
   250  		"key3": "value3",
   251  	}
   252  
   253  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   254  		interfaceToVariableSwallowError(expected))
   255  }
   256  
   257  func TestInterpolater_resourceVariableComplexMap(t *testing.T) {
   258  	lock := new(sync.RWMutex)
   259  	state := &State{
   260  		Modules: []*ModuleState{
   261  			&ModuleState{
   262  				Path: rootModulePath,
   263  				Resources: map[string]*ResourceState{
   264  					"aws_instance.web": &ResourceState{
   265  						Type: "aws_instance",
   266  						Primary: &InstanceState{
   267  							ID: "bar",
   268  							Attributes: map[string]string{
   269  								"amap.%":      "2",
   270  								"amap.key1.#": "2",
   271  								"amap.key1.0": "hello",
   272  								"amap.key1.1": "world",
   273  								"amap.key2.#": "1",
   274  								"amap.key2.0": "foo",
   275  							},
   276  						},
   277  					},
   278  				},
   279  			},
   280  		},
   281  	}
   282  
   283  	i := &Interpolater{
   284  		Module:    testModule(t, "interpolate-resource-variable"),
   285  		State:     state,
   286  		StateLock: lock,
   287  	}
   288  
   289  	scope := &InterpolationScope{
   290  		Path: rootModulePath,
   291  	}
   292  
   293  	expected := map[string]interface{}{
   294  		"key1": []interface{}{"hello", "world"},
   295  		"key2": []interface{}{"foo"},
   296  	}
   297  
   298  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   299  		interfaceToVariableSwallowError(expected))
   300  }
   301  
   302  func TestInterpolater_resourceVariable(t *testing.T) {
   303  	lock := new(sync.RWMutex)
   304  	state := &State{
   305  		Modules: []*ModuleState{
   306  			&ModuleState{
   307  				Path: rootModulePath,
   308  				Resources: map[string]*ResourceState{
   309  					"aws_instance.web": &ResourceState{
   310  						Type: "aws_instance",
   311  						Primary: &InstanceState{
   312  							ID: "bar",
   313  							Attributes: map[string]string{
   314  								"foo": "bar",
   315  							},
   316  						},
   317  					},
   318  				},
   319  			},
   320  		},
   321  	}
   322  
   323  	i := &Interpolater{
   324  		Module:    testModule(t, "interpolate-resource-variable"),
   325  		State:     state,
   326  		StateLock: lock,
   327  	}
   328  
   329  	scope := &InterpolationScope{
   330  		Path: rootModulePath,
   331  	}
   332  
   333  	testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   334  		Value: "bar",
   335  		Type:  ast.TypeString,
   336  	})
   337  }
   338  
   339  func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
   340  	// During the input walk, computed resource attributes may be entirely
   341  	// absent since we've not yet produced diffs that tell us what computed
   342  	// attributes to expect. In that case, interpolator tolerates it and
   343  	// indicates the value is computed.
   344  
   345  	lock := new(sync.RWMutex)
   346  	state := &State{
   347  		Modules: []*ModuleState{
   348  			&ModuleState{
   349  				Path:      rootModulePath,
   350  				Resources: map[string]*ResourceState{
   351  					// No resources at all yet, because we're still dealing
   352  					// with input and so the resources haven't been created.
   353  				},
   354  			},
   355  		},
   356  	}
   357  
   358  	{
   359  		i := &Interpolater{
   360  			Operation: walkInput,
   361  			Module:    testModule(t, "interpolate-resource-variable"),
   362  			State:     state,
   363  			StateLock: lock,
   364  		}
   365  
   366  		scope := &InterpolationScope{
   367  			Path: rootModulePath,
   368  		}
   369  
   370  		testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   371  			Value: config.UnknownVariableValue,
   372  			Type:  ast.TypeUnknown,
   373  		})
   374  	}
   375  
   376  	// This doesn't apply during other walks, like plan
   377  	{
   378  		i := &Interpolater{
   379  			Operation: walkPlan,
   380  			Module:    testModule(t, "interpolate-resource-variable"),
   381  			State:     state,
   382  			StateLock: lock,
   383  		}
   384  
   385  		scope := &InterpolationScope{
   386  			Path: rootModulePath,
   387  		}
   388  
   389  		testInterpolateErr(t, i, scope, "aws_instance.web.foo")
   390  	}
   391  }
   392  
   393  func TestInterpolater_resourceVariableMulti(t *testing.T) {
   394  	lock := new(sync.RWMutex)
   395  	state := &State{
   396  		Modules: []*ModuleState{
   397  			&ModuleState{
   398  				Path: rootModulePath,
   399  				Resources: map[string]*ResourceState{
   400  					"aws_instance.web": &ResourceState{
   401  						Type: "aws_instance",
   402  						Primary: &InstanceState{
   403  							ID: "bar",
   404  							Attributes: map[string]string{
   405  								"foo": config.UnknownVariableValue,
   406  							},
   407  						},
   408  					},
   409  				},
   410  			},
   411  		},
   412  	}
   413  
   414  	i := &Interpolater{
   415  		Module:    testModule(t, "interpolate-resource-variable"),
   416  		State:     state,
   417  		StateLock: lock,
   418  	}
   419  
   420  	scope := &InterpolationScope{
   421  		Path: rootModulePath,
   422  	}
   423  
   424  	testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
   425  		Type: ast.TypeList,
   426  		Value: []ast.Variable{
   427  			{
   428  				Type:  ast.TypeUnknown,
   429  				Value: config.UnknownVariableValue,
   430  			},
   431  		},
   432  	})
   433  }
   434  
   435  func TestInterpolater_resourceVariableMultiPartialUnknown(t *testing.T) {
   436  	lock := new(sync.RWMutex)
   437  	state := &State{
   438  		Modules: []*ModuleState{
   439  			&ModuleState{
   440  				Path: rootModulePath,
   441  				Resources: map[string]*ResourceState{
   442  					"aws_instance.web.0": &ResourceState{
   443  						Type: "aws_instance",
   444  						Primary: &InstanceState{
   445  							ID: "bar",
   446  							Attributes: map[string]string{
   447  								"foo": "1",
   448  							},
   449  						},
   450  					},
   451  					"aws_instance.web.1": &ResourceState{
   452  						Type: "aws_instance",
   453  						Primary: &InstanceState{
   454  							ID: "bar",
   455  							Attributes: map[string]string{
   456  								"foo": config.UnknownVariableValue,
   457  							},
   458  						},
   459  					},
   460  					"aws_instance.web.2": &ResourceState{
   461  						Type: "aws_instance",
   462  						Primary: &InstanceState{
   463  							ID: "bar",
   464  							Attributes: map[string]string{
   465  								"foo": "2",
   466  							},
   467  						},
   468  					},
   469  				},
   470  			},
   471  		},
   472  	}
   473  
   474  	i := &Interpolater{
   475  		Module:    testModule(t, "interpolate-resource-variable-multi"),
   476  		State:     state,
   477  		StateLock: lock,
   478  	}
   479  
   480  	scope := &InterpolationScope{
   481  		Path: rootModulePath,
   482  	}
   483  
   484  	testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
   485  		Type: ast.TypeList,
   486  		Value: []ast.Variable{
   487  			{
   488  				Type:  ast.TypeString,
   489  				Value: "1",
   490  			},
   491  			{
   492  				Type:  ast.TypeUnknown,
   493  				Value: config.UnknownVariableValue,
   494  			},
   495  			{
   496  				Type:  ast.TypeString,
   497  				Value: "2",
   498  			},
   499  		},
   500  	})
   501  }
   502  
   503  func TestInterpolater_resourceVariableMultiNoState(t *testing.T) {
   504  	// When evaluating a "splat" variable in a module that doesn't have
   505  	// any state yet, we should still be able to resolve to an empty
   506  	// list.
   507  	// See https://github.com/hashicorp/terraform/issues/14438 for an
   508  	// example of what we're testing for here.
   509  	lock := new(sync.RWMutex)
   510  	state := &State{
   511  		Modules: []*ModuleState{},
   512  	}
   513  
   514  	i := &Interpolater{
   515  		Module:    testModule(t, "interpolate-resource-variable-multi"),
   516  		State:     state,
   517  		StateLock: lock,
   518  		Operation: walkApply,
   519  	}
   520  
   521  	scope := &InterpolationScope{
   522  		Path: rootModulePath,
   523  	}
   524  
   525  	testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
   526  		Type:  ast.TypeList,
   527  		Value: []ast.Variable{},
   528  	})
   529  }
   530  
   531  // When a splat reference is made to an attribute that is a computed list,
   532  // the result should be unknown.
   533  func TestInterpolater_resourceVariableMultiList(t *testing.T) {
   534  	lock := new(sync.RWMutex)
   535  	state := &State{
   536  		Modules: []*ModuleState{
   537  			&ModuleState{
   538  				Path: rootModulePath,
   539  				Resources: map[string]*ResourceState{
   540  					"aws_instance.web.0": &ResourceState{
   541  						Type: "aws_instance",
   542  						Primary: &InstanceState{
   543  							ID: "bar",
   544  							Attributes: map[string]string{
   545  								"ip.#": config.UnknownVariableValue,
   546  							},
   547  						},
   548  					},
   549  
   550  					"aws_instance.web.1": &ResourceState{
   551  						Type: "aws_instance",
   552  						Primary: &InstanceState{
   553  							ID: "bar",
   554  							Attributes: map[string]string{
   555  								"ip.#": "0",
   556  							},
   557  						},
   558  					},
   559  				},
   560  			},
   561  		},
   562  	}
   563  
   564  	i := &Interpolater{
   565  		Module:    testModule(t, "interpolate-resource-variable"),
   566  		State:     state,
   567  		StateLock: lock,
   568  	}
   569  
   570  	scope := &InterpolationScope{
   571  		Path: rootModulePath,
   572  	}
   573  
   574  	testInterpolate(t, i, scope, "aws_instance.web.*.ip", ast.Variable{
   575  		Type: ast.TypeList,
   576  		Value: []ast.Variable{
   577  			{
   578  				Type:  ast.TypeUnknown,
   579  				Value: config.UnknownVariableValue,
   580  			},
   581  		},
   582  	})
   583  }
   584  
   585  func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) {
   586  	lock := new(sync.RWMutex)
   587  	state := &State{
   588  		Modules: []*ModuleState{
   589  			&ModuleState{
   590  				Path: rootModulePath,
   591  				Resources: map[string]*ResourceState{
   592  					"aws_instance.web.0": &ResourceState{
   593  						Type: "aws_instance",
   594  						Primary: &InstanceState{
   595  							ID:         "a",
   596  							Attributes: map[string]string{"foo": "a"},
   597  						},
   598  					},
   599  
   600  					"aws_instance.web.1": &ResourceState{
   601  						Type: "aws_instance",
   602  						Primary: &InstanceState{
   603  							ID:         "b",
   604  							Attributes: map[string]string{"foo": "b"},
   605  						},
   606  					},
   607  				},
   608  			},
   609  		},
   610  	}
   611  
   612  	i := &Interpolater{
   613  		Operation: walkApply,
   614  		Module:    testModule(t, "interpolate-multi-interp"),
   615  		State:     state,
   616  		StateLock: lock,
   617  	}
   618  
   619  	scope := &InterpolationScope{
   620  		Path: rootModulePath,
   621  	}
   622  
   623  	expected := []interface{}{"a", "b"}
   624  	testInterpolate(t, i, scope, "aws_instance.web.*.foo",
   625  		interfaceToVariableSwallowError(expected))
   626  }
   627  
   628  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   629  	variable, _ := hil.InterfaceToVariable(input)
   630  	return variable
   631  }
   632  
   633  func TestInterpolator_resourceMultiAttributes(t *testing.T) {
   634  	lock := new(sync.RWMutex)
   635  	state := &State{
   636  		Modules: []*ModuleState{
   637  			{
   638  				Path: rootModulePath,
   639  				Resources: map[string]*ResourceState{
   640  					"aws_route53_zone.yada": {
   641  						Type:         "aws_route53_zone",
   642  						Dependencies: []string{},
   643  						Primary: &InstanceState{
   644  							ID: "AAABBBCCCDDDEEE",
   645  							Attributes: map[string]string{
   646  								"name_servers.#": "4",
   647  								"name_servers.0": "ns-1334.awsdns-38.org",
   648  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   649  								"name_servers.2": "ns-498.awsdns-62.com",
   650  								"name_servers.3": "ns-601.awsdns-11.net",
   651  								"listeners.#":    "1",
   652  								"listeners.0":    "red",
   653  								"tags.%":         "1",
   654  								"tags.Name":      "reindeer",
   655  								"nothing.#":      "0",
   656  							},
   657  						},
   658  					},
   659  				},
   660  			},
   661  		},
   662  	}
   663  
   664  	i := &Interpolater{
   665  		Module:    testModule(t, "interpolate-multi-vars"),
   666  		StateLock: lock,
   667  		State:     state,
   668  	}
   669  
   670  	scope := &InterpolationScope{
   671  		Path: rootModulePath,
   672  	}
   673  
   674  	name_servers := []interface{}{
   675  		"ns-1334.awsdns-38.org",
   676  		"ns-1680.awsdns-18.co.uk",
   677  		"ns-498.awsdns-62.com",
   678  		"ns-601.awsdns-11.net",
   679  	}
   680  
   681  	// More than 1 element
   682  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers",
   683  		interfaceToVariableSwallowError(name_servers))
   684  
   685  	// Exactly 1 element
   686  	testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners",
   687  		interfaceToVariableSwallowError([]interface{}{"red"}))
   688  
   689  	// Zero elements
   690  	testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing",
   691  		interfaceToVariableSwallowError([]interface{}{}))
   692  
   693  	// Maps still need to work
   694  	testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
   695  		Value: "reindeer",
   696  		Type:  ast.TypeString,
   697  	})
   698  }
   699  
   700  func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
   701  	i := getInterpolaterFixture(t)
   702  	scope := &InterpolationScope{
   703  		Path: rootModulePath,
   704  	}
   705  
   706  	name_servers := []interface{}{
   707  		"ns-1334.awsdns-38.org",
   708  		"ns-1680.awsdns-18.co.uk",
   709  		"ns-498.awsdns-62.com",
   710  		"ns-601.awsdns-11.net",
   711  		"ns-000.awsdns-38.org",
   712  		"ns-444.awsdns-18.co.uk",
   713  		"ns-999.awsdns-62.com",
   714  		"ns-666.awsdns-11.net",
   715  	}
   716  
   717  	// More than 1 element
   718  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers",
   719  		interfaceToVariableSwallowError(name_servers[:4]))
   720  
   721  	// More than 1 element in both
   722  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers",
   723  		interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]}))
   724  
   725  	// Exactly 1 element
   726  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners",
   727  		interfaceToVariableSwallowError([]interface{}{"red"}))
   728  
   729  	// Exactly 1 element in both
   730  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners",
   731  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}}))
   732  
   733  	// Zero elements
   734  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing",
   735  		interfaceToVariableSwallowError([]interface{}{}))
   736  
   737  	// Zero + 1 element
   738  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special",
   739  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}}))
   740  
   741  	// Maps still need to work
   742  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
   743  		Value: "reindeer",
   744  		Type:  ast.TypeString,
   745  	})
   746  
   747  	// Maps still need to work in both
   748  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name",
   749  		interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"}))
   750  }
   751  
   752  func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
   753  	lock := new(sync.RWMutex)
   754  	// The state would never be written with an UnknownVariableValue in it, but
   755  	// it can/does exist that way in memory during the plan phase.
   756  	state := &State{
   757  		Modules: []*ModuleState{
   758  			&ModuleState{
   759  				Path: rootModulePath,
   760  				Resources: map[string]*ResourceState{
   761  					"aws_route53_zone.yada": &ResourceState{
   762  						Type: "aws_route53_zone",
   763  						Primary: &InstanceState{
   764  							ID: "z-abc123",
   765  							Attributes: map[string]string{
   766  								"name_servers.#": config.UnknownVariableValue,
   767  							},
   768  						},
   769  					},
   770  				},
   771  			},
   772  		},
   773  	}
   774  	i := &Interpolater{
   775  		Module:    testModule(t, "interpolate-multi-vars"),
   776  		StateLock: lock,
   777  		State:     state,
   778  	}
   779  
   780  	scope := &InterpolationScope{
   781  		Path: rootModulePath,
   782  	}
   783  
   784  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
   785  		Value: config.UnknownVariableValue,
   786  		Type:  ast.TypeUnknown,
   787  	})
   788  }
   789  
   790  func TestInterpolator_resourceAttributeComputed(t *testing.T) {
   791  	lock := new(sync.RWMutex)
   792  	// The state would never be written with an UnknownVariableValue in it, but
   793  	// it can/does exist that way in memory during the plan phase.
   794  	state := &State{
   795  		Modules: []*ModuleState{
   796  			&ModuleState{
   797  				Path: rootModulePath,
   798  				Resources: map[string]*ResourceState{
   799  					"aws_route53_zone.yada": &ResourceState{
   800  						Type: "aws_route53_zone",
   801  						Primary: &InstanceState{
   802  							ID: "z-abc123",
   803  							Attributes: map[string]string{
   804  								"id": config.UnknownVariableValue,
   805  							},
   806  						},
   807  					},
   808  				},
   809  			},
   810  		},
   811  	}
   812  	i := &Interpolater{
   813  		Module:    testModule(t, "interpolate-multi-vars"),
   814  		StateLock: lock,
   815  		State:     state,
   816  	}
   817  
   818  	scope := &InterpolationScope{
   819  		Path: rootModulePath,
   820  	}
   821  
   822  	testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{
   823  		Value: config.UnknownVariableValue,
   824  		Type:  ast.TypeUnknown,
   825  	})
   826  }
   827  
   828  func TestInterpolater_selfVarWithoutResource(t *testing.T) {
   829  	i := &Interpolater{}
   830  
   831  	scope := &InterpolationScope{
   832  		Path: rootModulePath,
   833  	}
   834  
   835  	v, err := config.NewInterpolatedVariable("self.name")
   836  	if err != nil {
   837  		t.Fatalf("err: %s", err)
   838  	}
   839  
   840  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
   841  	if err == nil {
   842  		t.Fatalf("expected err, got none")
   843  	}
   844  }
   845  
   846  func TestInterpolator_interpolatedListOrder(t *testing.T) {
   847  	state := &State{
   848  		Modules: []*ModuleState{
   849  			&ModuleState{
   850  				Path: rootModulePath,
   851  				Resources: map[string]*ResourceState{
   852  					"aws_route53_zone.yada": &ResourceState{
   853  						Type:         "aws_route53_zone",
   854  						Dependencies: []string{},
   855  						Primary: &InstanceState{
   856  							ID: "null",
   857  							Attributes: map[string]string{
   858  								"foo.#":  "12",
   859  								"foo.0":  "a",
   860  								"foo.1":  "b",
   861  								"foo.2":  "c",
   862  								"foo.3":  "d",
   863  								"foo.4":  "e",
   864  								"foo.5":  "f",
   865  								"foo.6":  "g",
   866  								"foo.7":  "h",
   867  								"foo.8":  "i",
   868  								"foo.9":  "j",
   869  								"foo.10": "k",
   870  								"foo.11": "l",
   871  							},
   872  						},
   873  					},
   874  				},
   875  			},
   876  		},
   877  	}
   878  
   879  	i := &Interpolater{
   880  		Module:    testModule(t, "interpolate-multi-vars"),
   881  		StateLock: new(sync.RWMutex),
   882  		State:     state,
   883  	}
   884  
   885  	scope := &InterpolationScope{
   886  		Path: rootModulePath,
   887  	}
   888  
   889  	list := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}
   890  
   891  	testInterpolate(t, i, scope, "aws_route53_zone.yada.foo",
   892  		interfaceToVariableSwallowError(list))
   893  }
   894  
   895  func getInterpolaterFixture(t *testing.T) *Interpolater {
   896  	lock := new(sync.RWMutex)
   897  	state := &State{
   898  		Modules: []*ModuleState{
   899  			&ModuleState{
   900  				Path: rootModulePath,
   901  				Resources: map[string]*ResourceState{
   902  					"aws_route53_zone.terra.0": &ResourceState{
   903  						Type:         "aws_route53_zone",
   904  						Dependencies: []string{},
   905  						Primary: &InstanceState{
   906  							ID: "AAABBBCCCDDDEEE",
   907  							Attributes: map[string]string{
   908  								"name_servers.#": "4",
   909  								"name_servers.0": "ns-1334.awsdns-38.org",
   910  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   911  								"name_servers.2": "ns-498.awsdns-62.com",
   912  								"name_servers.3": "ns-601.awsdns-11.net",
   913  								"listeners.#":    "1",
   914  								"listeners.0":    "red",
   915  								"tags.%":         "1",
   916  								"tags.Name":      "reindeer",
   917  								"nothing.#":      "0",
   918  							},
   919  						},
   920  					},
   921  					"aws_route53_zone.terra.1": &ResourceState{
   922  						Type:         "aws_route53_zone",
   923  						Dependencies: []string{},
   924  						Primary: &InstanceState{
   925  							ID: "EEEFFFGGGHHHIII",
   926  							Attributes: map[string]string{
   927  								"name_servers.#": "4",
   928  								"name_servers.0": "ns-000.awsdns-38.org",
   929  								"name_servers.1": "ns-444.awsdns-18.co.uk",
   930  								"name_servers.2": "ns-999.awsdns-62.com",
   931  								"name_servers.3": "ns-666.awsdns-11.net",
   932  								"listeners.#":    "1",
   933  								"listeners.0":    "blue",
   934  								"special.#":      "1",
   935  								"special.0":      "extra",
   936  								"tags.%":         "1",
   937  								"tags.Name":      "white-hart",
   938  								"nothing.#":      "0",
   939  							},
   940  						},
   941  					},
   942  				},
   943  			},
   944  		},
   945  	}
   946  
   947  	return &Interpolater{
   948  		Module:    testModule(t, "interpolate-multi-vars"),
   949  		StateLock: lock,
   950  		State:     state,
   951  	}
   952  }
   953  
   954  func TestInterpolator_nestedMapsAndLists(t *testing.T) {
   955  	state := &State{
   956  		Modules: []*ModuleState{
   957  			&ModuleState{
   958  				Path: rootModulePath,
   959  				Resources: map[string]*ResourceState{
   960  					"aws_route53_zone.yada": &ResourceState{
   961  						Type:         "aws_route53_zone",
   962  						Dependencies: []string{},
   963  						Primary: &InstanceState{
   964  							ID: "null",
   965  							Attributes: map[string]string{
   966  								"list_of_map.#":       "2",
   967  								"list_of_map.0.%":     "1",
   968  								"list_of_map.0.a":     "1",
   969  								"list_of_map.1.%":     "1",
   970  								"list_of_map.1.b":     "2",
   971  								"map_of_list.%":       "2",
   972  								"map_of_list.list2.#": "1",
   973  								"map_of_list.list2.0": "b",
   974  								"map_of_list.list1.#": "1",
   975  								"map_of_list.list1.0": "a",
   976  							},
   977  						},
   978  					},
   979  				},
   980  			},
   981  		},
   982  	}
   983  
   984  	i := &Interpolater{
   985  		Module:    testModule(t, "interpolate-multi-vars"),
   986  		StateLock: new(sync.RWMutex),
   987  		State:     state,
   988  	}
   989  
   990  	scope := &InterpolationScope{
   991  		Path: rootModulePath,
   992  	}
   993  
   994  	listOfMap := []interface{}{
   995  		map[string]interface{}{"a": "1"},
   996  		map[string]interface{}{"b": "2"},
   997  	}
   998  
   999  	mapOfList := map[string]interface{}{
  1000  		"list1": []interface{}{"a"},
  1001  		"list2": []interface{}{"b"},
  1002  	}
  1003  
  1004  	testInterpolate(t, i, scope, "aws_route53_zone.yada.list_of_map",
  1005  		interfaceToVariableSwallowError(listOfMap))
  1006  	testInterpolate(t, i, scope, `aws_route53_zone.yada.map_of_list`,
  1007  		interfaceToVariableSwallowError(mapOfList))
  1008  }
  1009  
  1010  func TestInterpolator_sets(t *testing.T) {
  1011  	state := &State{
  1012  		Modules: []*ModuleState{
  1013  			&ModuleState{
  1014  				Path: rootModulePath,
  1015  				Resources: map[string]*ResourceState{
  1016  					"aws_route53_zone.yada": &ResourceState{
  1017  						Type:         "aws_network_interface",
  1018  						Dependencies: []string{},
  1019  						Primary: &InstanceState{
  1020  							ID: "null",
  1021  							Attributes: map[string]string{
  1022  								"private_ips.#":          "1",
  1023  								"private_ips.3977356764": "10.42.16.179",
  1024  							},
  1025  						},
  1026  					},
  1027  				},
  1028  			},
  1029  		},
  1030  	}
  1031  
  1032  	i := &Interpolater{
  1033  		Module:    testModule(t, "interpolate-multi-vars"),
  1034  		StateLock: new(sync.RWMutex),
  1035  		State:     state,
  1036  	}
  1037  
  1038  	scope := &InterpolationScope{
  1039  		Path: rootModulePath,
  1040  	}
  1041  
  1042  	set := []interface{}{"10.42.16.179"}
  1043  
  1044  	testInterpolate(t, i, scope, "aws_route53_zone.yada.private_ips",
  1045  		interfaceToVariableSwallowError(set))
  1046  }
  1047  
  1048  // When a splat reference is made to a resource that is unknown, we should
  1049  // return an empty list rather than panicking.
  1050  func TestInterpolater_resourceUnknownVariableList(t *testing.T) {
  1051  	i := &Interpolater{
  1052  		Module:    testModule(t, "plan-computed-data-resource"),
  1053  		State:     NewState(), // state,
  1054  		StateLock: new(sync.RWMutex),
  1055  	}
  1056  
  1057  	scope := &InterpolationScope{
  1058  		Path: rootModulePath,
  1059  	}
  1060  
  1061  	testInterpolate(t, i, scope, "aws_vpc.bar.*.foo",
  1062  		interfaceToVariableSwallowError([]interface{}{}))
  1063  }
  1064  
  1065  func TestInterpolater_terraformEnv(t *testing.T) {
  1066  	i := &Interpolater{
  1067  		Meta: &ContextMeta{Env: "foo"},
  1068  	}
  1069  
  1070  	scope := &InterpolationScope{
  1071  		Path: rootModulePath,
  1072  	}
  1073  
  1074  	testInterpolate(t, i, scope, "terraform.env", ast.Variable{
  1075  		Value: "foo",
  1076  		Type:  ast.TypeString,
  1077  	})
  1078  }
  1079  
  1080  func TestInterpolater_terraformInvalid(t *testing.T) {
  1081  	i := &Interpolater{
  1082  		Meta: &ContextMeta{Env: "foo"},
  1083  	}
  1084  
  1085  	scope := &InterpolationScope{
  1086  		Path: rootModulePath,
  1087  	}
  1088  
  1089  	testInterpolateErr(t, i, scope, "terraform.nope")
  1090  }
  1091  
  1092  func testInterpolate(
  1093  	t *testing.T, i *Interpolater,
  1094  	scope *InterpolationScope,
  1095  	n string, expectedVar ast.Variable) {
  1096  	v, err := config.NewInterpolatedVariable(n)
  1097  	if err != nil {
  1098  		t.Fatalf("err: %s", err)
  1099  	}
  1100  
  1101  	actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
  1102  		"foo": v,
  1103  	})
  1104  	if err != nil {
  1105  		t.Fatalf("err: %s", err)
  1106  	}
  1107  
  1108  	expected := map[string]ast.Variable{
  1109  		"foo": expectedVar,
  1110  	}
  1111  	if !reflect.DeepEqual(actual, expected) {
  1112  		spew.Config.DisableMethods = true
  1113  		t.Fatalf("%q:\n\n  actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected,
  1114  			spew.Sdump(actual), spew.Sdump(expected))
  1115  	}
  1116  }
  1117  
  1118  func testInterpolateErr(
  1119  	t *testing.T, i *Interpolater,
  1120  	scope *InterpolationScope,
  1121  	n string) {
  1122  	v, err := config.NewInterpolatedVariable(n)
  1123  	if err != nil {
  1124  		t.Fatalf("err: %s", err)
  1125  	}
  1126  
  1127  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
  1128  		"foo": v,
  1129  	})
  1130  	if err == nil {
  1131  		t.Fatalf("%q: succeeded, but wanted error", n)
  1132  	}
  1133  }