github.com/aspring/terraform@v0.8.2-0.20161216122603-6a8619a5db2e/terraform/interpolate_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"sort"
     8  	"sync"
     9  	"testing"
    10  
    11  	"github.com/davecgh/go-spew/spew"
    12  	"github.com/hashicorp/hil"
    13  	"github.com/hashicorp/hil/ast"
    14  	"github.com/hashicorp/terraform/config"
    15  )
    16  
    17  func TestInterpolater_simpleVar(t *testing.T) {
    18  	i := &Interpolater{}
    19  	scope := &InterpolationScope{}
    20  	testInterpolateErr(t, i, scope, "simple")
    21  }
    22  
    23  func TestInterpolater_countIndex(t *testing.T) {
    24  	i := &Interpolater{}
    25  
    26  	scope := &InterpolationScope{
    27  		Path:     rootModulePath,
    28  		Resource: &Resource{CountIndex: 42},
    29  	}
    30  
    31  	testInterpolate(t, i, scope, "count.index", ast.Variable{
    32  		Value: 42,
    33  		Type:  ast.TypeInt,
    34  	})
    35  }
    36  
    37  func TestInterpolater_countIndexInWrongContext(t *testing.T) {
    38  	i := &Interpolater{}
    39  
    40  	scope := &InterpolationScope{
    41  		Path: rootModulePath,
    42  	}
    43  
    44  	n := "count.index"
    45  
    46  	v, err := config.NewInterpolatedVariable(n)
    47  	if err != nil {
    48  		t.Fatalf("err: %s", err)
    49  	}
    50  
    51  	expectedErr := fmt.Errorf("foo: count.index is only valid within resources")
    52  
    53  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
    54  		"foo": v,
    55  	})
    56  
    57  	if !reflect.DeepEqual(expectedErr, err) {
    58  		t.Fatalf("expected: %#v, got %#v", expectedErr, err)
    59  	}
    60  }
    61  
    62  func TestInterpolater_moduleVariable(t *testing.T) {
    63  	lock := new(sync.RWMutex)
    64  	state := &State{
    65  		Modules: []*ModuleState{
    66  			&ModuleState{
    67  				Path: rootModulePath,
    68  				Resources: map[string]*ResourceState{
    69  					"aws_instance.web": &ResourceState{
    70  						Type: "aws_instance",
    71  						Primary: &InstanceState{
    72  							ID: "bar",
    73  						},
    74  					},
    75  				},
    76  			},
    77  			&ModuleState{
    78  				Path: []string{RootModuleName, "child"},
    79  				Outputs: map[string]*OutputState{
    80  					"foo": &OutputState{
    81  						Type:  "string",
    82  						Value: "bar",
    83  					},
    84  				},
    85  			},
    86  		},
    87  	}
    88  
    89  	i := &Interpolater{
    90  		State:     state,
    91  		StateLock: lock,
    92  	}
    93  
    94  	scope := &InterpolationScope{
    95  		Path: rootModulePath,
    96  	}
    97  
    98  	testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
    99  		Value: "bar",
   100  		Type:  ast.TypeString,
   101  	})
   102  }
   103  
   104  func TestInterpolater_pathCwd(t *testing.T) {
   105  	i := &Interpolater{}
   106  	scope := &InterpolationScope{}
   107  
   108  	expected, err := os.Getwd()
   109  	if err != nil {
   110  		t.Fatalf("err: %s", err)
   111  	}
   112  
   113  	testInterpolate(t, i, scope, "path.cwd", ast.Variable{
   114  		Value: expected,
   115  		Type:  ast.TypeString,
   116  	})
   117  }
   118  
   119  func TestInterpolater_pathModule(t *testing.T) {
   120  	mod := testModule(t, "interpolate-path-module")
   121  	i := &Interpolater{
   122  		Module: mod,
   123  	}
   124  	scope := &InterpolationScope{
   125  		Path: []string{RootModuleName, "child"},
   126  	}
   127  
   128  	path := mod.Child([]string{"child"}).Config().Dir
   129  	testInterpolate(t, i, scope, "path.module", ast.Variable{
   130  		Value: path,
   131  		Type:  ast.TypeString,
   132  	})
   133  }
   134  
   135  func TestInterpolater_pathRoot(t *testing.T) {
   136  	mod := testModule(t, "interpolate-path-module")
   137  	i := &Interpolater{
   138  		Module: mod,
   139  	}
   140  	scope := &InterpolationScope{
   141  		Path: []string{RootModuleName, "child"},
   142  	}
   143  
   144  	path := mod.Config().Dir
   145  	testInterpolate(t, i, scope, "path.root", ast.Variable{
   146  		Value: path,
   147  		Type:  ast.TypeString,
   148  	})
   149  }
   150  
   151  func TestInterpolater_resourceVariableMap(t *testing.T) {
   152  	lock := new(sync.RWMutex)
   153  	state := &State{
   154  		Modules: []*ModuleState{
   155  			&ModuleState{
   156  				Path: rootModulePath,
   157  				Resources: map[string]*ResourceState{
   158  					"aws_instance.web": &ResourceState{
   159  						Type: "aws_instance",
   160  						Primary: &InstanceState{
   161  							ID: "bar",
   162  							Attributes: map[string]string{
   163  								"amap.%":    "3",
   164  								"amap.key1": "value1",
   165  								"amap.key2": "value2",
   166  								"amap.key3": "value3",
   167  							},
   168  						},
   169  					},
   170  				},
   171  			},
   172  		},
   173  	}
   174  
   175  	i := &Interpolater{
   176  		Module:    testModule(t, "interpolate-resource-variable"),
   177  		State:     state,
   178  		StateLock: lock,
   179  	}
   180  
   181  	scope := &InterpolationScope{
   182  		Path: rootModulePath,
   183  	}
   184  
   185  	expected := map[string]interface{}{
   186  		"key1": "value1",
   187  		"key2": "value2",
   188  		"key3": "value3",
   189  	}
   190  
   191  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   192  		interfaceToVariableSwallowError(expected))
   193  }
   194  
   195  func TestInterpolater_resourceVariableComplexMap(t *testing.T) {
   196  	lock := new(sync.RWMutex)
   197  	state := &State{
   198  		Modules: []*ModuleState{
   199  			&ModuleState{
   200  				Path: rootModulePath,
   201  				Resources: map[string]*ResourceState{
   202  					"aws_instance.web": &ResourceState{
   203  						Type: "aws_instance",
   204  						Primary: &InstanceState{
   205  							ID: "bar",
   206  							Attributes: map[string]string{
   207  								"amap.%":      "2",
   208  								"amap.key1.#": "2",
   209  								"amap.key1.0": "hello",
   210  								"amap.key1.1": "world",
   211  								"amap.key2.#": "1",
   212  								"amap.key2.0": "foo",
   213  							},
   214  						},
   215  					},
   216  				},
   217  			},
   218  		},
   219  	}
   220  
   221  	i := &Interpolater{
   222  		Module:    testModule(t, "interpolate-resource-variable"),
   223  		State:     state,
   224  		StateLock: lock,
   225  	}
   226  
   227  	scope := &InterpolationScope{
   228  		Path: rootModulePath,
   229  	}
   230  
   231  	expected := map[string]interface{}{
   232  		"key1": []interface{}{"hello", "world"},
   233  		"key2": []interface{}{"foo"},
   234  	}
   235  
   236  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   237  		interfaceToVariableSwallowError(expected))
   238  }
   239  
   240  func TestInterpolater_resourceVariable(t *testing.T) {
   241  	lock := new(sync.RWMutex)
   242  	state := &State{
   243  		Modules: []*ModuleState{
   244  			&ModuleState{
   245  				Path: rootModulePath,
   246  				Resources: map[string]*ResourceState{
   247  					"aws_instance.web": &ResourceState{
   248  						Type: "aws_instance",
   249  						Primary: &InstanceState{
   250  							ID: "bar",
   251  							Attributes: map[string]string{
   252  								"foo": "bar",
   253  							},
   254  						},
   255  					},
   256  				},
   257  			},
   258  		},
   259  	}
   260  
   261  	i := &Interpolater{
   262  		Module:    testModule(t, "interpolate-resource-variable"),
   263  		State:     state,
   264  		StateLock: lock,
   265  	}
   266  
   267  	scope := &InterpolationScope{
   268  		Path: rootModulePath,
   269  	}
   270  
   271  	testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   272  		Value: "bar",
   273  		Type:  ast.TypeString,
   274  	})
   275  }
   276  
   277  func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
   278  	// During the input walk, computed resource attributes may be entirely
   279  	// absent since we've not yet produced diffs that tell us what computed
   280  	// attributes to expect. In that case, interpolator tolerates it and
   281  	// indicates the value is computed.
   282  
   283  	lock := new(sync.RWMutex)
   284  	state := &State{
   285  		Modules: []*ModuleState{
   286  			&ModuleState{
   287  				Path:      rootModulePath,
   288  				Resources: map[string]*ResourceState{
   289  				// No resources at all yet, because we're still dealing
   290  				// with input and so the resources haven't been created.
   291  				},
   292  			},
   293  		},
   294  	}
   295  
   296  	{
   297  		i := &Interpolater{
   298  			Operation: walkInput,
   299  			Module:    testModule(t, "interpolate-resource-variable"),
   300  			State:     state,
   301  			StateLock: lock,
   302  		}
   303  
   304  		scope := &InterpolationScope{
   305  			Path: rootModulePath,
   306  		}
   307  
   308  		testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   309  			Value: config.UnknownVariableValue,
   310  			Type:  ast.TypeUnknown,
   311  		})
   312  	}
   313  
   314  	// This doesn't apply during other walks, like plan
   315  	{
   316  		i := &Interpolater{
   317  			Operation: walkPlan,
   318  			Module:    testModule(t, "interpolate-resource-variable"),
   319  			State:     state,
   320  			StateLock: lock,
   321  		}
   322  
   323  		scope := &InterpolationScope{
   324  			Path: rootModulePath,
   325  		}
   326  
   327  		testInterpolateErr(t, i, scope, "aws_instance.web.foo")
   328  	}
   329  }
   330  
   331  func TestInterpolater_resourceVariableMulti(t *testing.T) {
   332  	lock := new(sync.RWMutex)
   333  	state := &State{
   334  		Modules: []*ModuleState{
   335  			&ModuleState{
   336  				Path: rootModulePath,
   337  				Resources: map[string]*ResourceState{
   338  					"aws_instance.web": &ResourceState{
   339  						Type: "aws_instance",
   340  						Primary: &InstanceState{
   341  							ID: "bar",
   342  							Attributes: map[string]string{
   343  								"foo": config.UnknownVariableValue,
   344  							},
   345  						},
   346  					},
   347  				},
   348  			},
   349  		},
   350  	}
   351  
   352  	i := &Interpolater{
   353  		Module:    testModule(t, "interpolate-resource-variable"),
   354  		State:     state,
   355  		StateLock: lock,
   356  	}
   357  
   358  	scope := &InterpolationScope{
   359  		Path: rootModulePath,
   360  	}
   361  
   362  	testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
   363  		Value: config.UnknownVariableValue,
   364  		Type:  ast.TypeUnknown,
   365  	})
   366  }
   367  
   368  // When a splat reference is made to an attribute that is a computed list,
   369  // the result should be unknown.
   370  func TestInterpolater_resourceVariableMultiList(t *testing.T) {
   371  	lock := new(sync.RWMutex)
   372  	state := &State{
   373  		Modules: []*ModuleState{
   374  			&ModuleState{
   375  				Path: rootModulePath,
   376  				Resources: map[string]*ResourceState{
   377  					"aws_instance.web.0": &ResourceState{
   378  						Type: "aws_instance",
   379  						Primary: &InstanceState{
   380  							ID: "bar",
   381  							Attributes: map[string]string{
   382  								"ip.#": config.UnknownVariableValue,
   383  							},
   384  						},
   385  					},
   386  
   387  					"aws_instance.web.1": &ResourceState{
   388  						Type: "aws_instance",
   389  						Primary: &InstanceState{
   390  							ID: "bar",
   391  							Attributes: map[string]string{
   392  								"ip.#": "0",
   393  							},
   394  						},
   395  					},
   396  				},
   397  			},
   398  		},
   399  	}
   400  
   401  	i := &Interpolater{
   402  		Module:    testModule(t, "interpolate-resource-variable"),
   403  		State:     state,
   404  		StateLock: lock,
   405  	}
   406  
   407  	scope := &InterpolationScope{
   408  		Path: rootModulePath,
   409  	}
   410  
   411  	testInterpolate(t, i, scope, "aws_instance.web.*.ip", ast.Variable{
   412  		Value: config.UnknownVariableValue,
   413  		Type:  ast.TypeUnknown,
   414  	})
   415  }
   416  
   417  func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) {
   418  	lock := new(sync.RWMutex)
   419  	state := &State{
   420  		Modules: []*ModuleState{
   421  			&ModuleState{
   422  				Path: rootModulePath,
   423  				Resources: map[string]*ResourceState{
   424  					"aws_instance.web.0": &ResourceState{
   425  						Type: "aws_instance",
   426  						Primary: &InstanceState{
   427  							ID:         "a",
   428  							Attributes: map[string]string{"foo": "a"},
   429  						},
   430  					},
   431  
   432  					"aws_instance.web.1": &ResourceState{
   433  						Type: "aws_instance",
   434  						Primary: &InstanceState{
   435  							ID:         "b",
   436  							Attributes: map[string]string{"foo": "b"},
   437  						},
   438  					},
   439  				},
   440  			},
   441  		},
   442  	}
   443  
   444  	i := &Interpolater{
   445  		Operation: walkApply,
   446  		Module:    testModule(t, "interpolate-multi-interp"),
   447  		State:     state,
   448  		StateLock: lock,
   449  	}
   450  
   451  	scope := &InterpolationScope{
   452  		Path: rootModulePath,
   453  	}
   454  
   455  	expected := []interface{}{"a", "b"}
   456  	testInterpolate(t, i, scope, "aws_instance.web.*.foo",
   457  		interfaceToVariableSwallowError(expected))
   458  }
   459  
   460  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   461  	variable, _ := hil.InterfaceToVariable(input)
   462  	return variable
   463  }
   464  
   465  func TestInterpolator_resourceMultiAttributes(t *testing.T) {
   466  	lock := new(sync.RWMutex)
   467  	state := &State{
   468  		Modules: []*ModuleState{
   469  			{
   470  				Path: rootModulePath,
   471  				Resources: map[string]*ResourceState{
   472  					"aws_route53_zone.yada": {
   473  						Type:         "aws_route53_zone",
   474  						Dependencies: []string{},
   475  						Primary: &InstanceState{
   476  							ID: "AAABBBCCCDDDEEE",
   477  							Attributes: map[string]string{
   478  								"name_servers.#": "4",
   479  								"name_servers.0": "ns-1334.awsdns-38.org",
   480  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   481  								"name_servers.2": "ns-498.awsdns-62.com",
   482  								"name_servers.3": "ns-601.awsdns-11.net",
   483  								"listeners.#":    "1",
   484  								"listeners.0":    "red",
   485  								"tags.%":         "1",
   486  								"tags.Name":      "reindeer",
   487  								"nothing.#":      "0",
   488  							},
   489  						},
   490  					},
   491  				},
   492  			},
   493  		},
   494  	}
   495  
   496  	i := &Interpolater{
   497  		Module:    testModule(t, "interpolate-multi-vars"),
   498  		StateLock: lock,
   499  		State:     state,
   500  	}
   501  
   502  	scope := &InterpolationScope{
   503  		Path: rootModulePath,
   504  	}
   505  
   506  	name_servers := []interface{}{
   507  		"ns-1334.awsdns-38.org",
   508  		"ns-1680.awsdns-18.co.uk",
   509  		"ns-498.awsdns-62.com",
   510  		"ns-601.awsdns-11.net",
   511  	}
   512  
   513  	// More than 1 element
   514  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers",
   515  		interfaceToVariableSwallowError(name_servers))
   516  
   517  	// Exactly 1 element
   518  	testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners",
   519  		interfaceToVariableSwallowError([]interface{}{"red"}))
   520  
   521  	// Zero elements
   522  	testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing",
   523  		interfaceToVariableSwallowError([]interface{}{}))
   524  
   525  	// Maps still need to work
   526  	testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
   527  		Value: "reindeer",
   528  		Type:  ast.TypeString,
   529  	})
   530  }
   531  
   532  func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
   533  	i := getInterpolaterFixture(t)
   534  	scope := &InterpolationScope{
   535  		Path: rootModulePath,
   536  	}
   537  
   538  	name_servers := []interface{}{
   539  		"ns-1334.awsdns-38.org",
   540  		"ns-1680.awsdns-18.co.uk",
   541  		"ns-498.awsdns-62.com",
   542  		"ns-601.awsdns-11.net",
   543  		"ns-000.awsdns-38.org",
   544  		"ns-444.awsdns-18.co.uk",
   545  		"ns-999.awsdns-62.com",
   546  		"ns-666.awsdns-11.net",
   547  	}
   548  
   549  	// More than 1 element
   550  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers",
   551  		interfaceToVariableSwallowError(name_servers[:4]))
   552  
   553  	// More than 1 element in both
   554  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers",
   555  		interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]}))
   556  
   557  	// Exactly 1 element
   558  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners",
   559  		interfaceToVariableSwallowError([]interface{}{"red"}))
   560  
   561  	// Exactly 1 element in both
   562  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners",
   563  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}}))
   564  
   565  	// Zero elements
   566  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing",
   567  		interfaceToVariableSwallowError([]interface{}{}))
   568  
   569  	// Zero + 1 element
   570  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special",
   571  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}}))
   572  
   573  	// Maps still need to work
   574  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
   575  		Value: "reindeer",
   576  		Type:  ast.TypeString,
   577  	})
   578  
   579  	// Maps still need to work in both
   580  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name",
   581  		interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"}))
   582  }
   583  
   584  func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
   585  	lock := new(sync.RWMutex)
   586  	// The state would never be written with an UnknownVariableValue in it, but
   587  	// it can/does exist that way in memory during the plan phase.
   588  	state := &State{
   589  		Modules: []*ModuleState{
   590  			&ModuleState{
   591  				Path: rootModulePath,
   592  				Resources: map[string]*ResourceState{
   593  					"aws_route53_zone.yada": &ResourceState{
   594  						Type: "aws_route53_zone",
   595  						Primary: &InstanceState{
   596  							ID: "z-abc123",
   597  							Attributes: map[string]string{
   598  								"name_servers.#": config.UnknownVariableValue,
   599  							},
   600  						},
   601  					},
   602  				},
   603  			},
   604  		},
   605  	}
   606  	i := &Interpolater{
   607  		Module:    testModule(t, "interpolate-multi-vars"),
   608  		StateLock: lock,
   609  		State:     state,
   610  	}
   611  
   612  	scope := &InterpolationScope{
   613  		Path: rootModulePath,
   614  	}
   615  
   616  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
   617  		Value: config.UnknownVariableValue,
   618  		Type:  ast.TypeUnknown,
   619  	})
   620  }
   621  
   622  func TestInterpolator_resourceAttributeComputed(t *testing.T) {
   623  	lock := new(sync.RWMutex)
   624  	// The state would never be written with an UnknownVariableValue in it, but
   625  	// it can/does exist that way in memory during the plan phase.
   626  	state := &State{
   627  		Modules: []*ModuleState{
   628  			&ModuleState{
   629  				Path: rootModulePath,
   630  				Resources: map[string]*ResourceState{
   631  					"aws_route53_zone.yada": &ResourceState{
   632  						Type: "aws_route53_zone",
   633  						Primary: &InstanceState{
   634  							ID: "z-abc123",
   635  							Attributes: map[string]string{
   636  								"id": config.UnknownVariableValue,
   637  							},
   638  						},
   639  					},
   640  				},
   641  			},
   642  		},
   643  	}
   644  	i := &Interpolater{
   645  		Module:    testModule(t, "interpolate-multi-vars"),
   646  		StateLock: lock,
   647  		State:     state,
   648  	}
   649  
   650  	scope := &InterpolationScope{
   651  		Path: rootModulePath,
   652  	}
   653  
   654  	testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{
   655  		Value: config.UnknownVariableValue,
   656  		Type:  ast.TypeUnknown,
   657  	})
   658  }
   659  
   660  func TestInterpolater_selfVarWithoutResource(t *testing.T) {
   661  	i := &Interpolater{}
   662  
   663  	scope := &InterpolationScope{
   664  		Path: rootModulePath,
   665  	}
   666  
   667  	v, err := config.NewInterpolatedVariable("self.name")
   668  	if err != nil {
   669  		t.Fatalf("err: %s", err)
   670  	}
   671  
   672  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
   673  	if err == nil {
   674  		t.Fatalf("expected err, got none")
   675  	}
   676  }
   677  
   678  // Verify sorting by key index number
   679  func TestInterpolator_indexKeySort(t *testing.T) {
   680  	keys := []string{"a.1", "a.2", "a.10", "a.20", "a.3"}
   681  	sorted := []string{"a.1", "a.2", "a.3", "a.10", "a.20"}
   682  
   683  	sort.Sort(indexKeys(keys))
   684  	for i := range keys {
   685  		if keys[i] != sorted[i] {
   686  			t.Fatalf("indexes out of order\nexpected: %q\ngot: %q", sorted, keys)
   687  		}
   688  	}
   689  }
   690  
   691  func TestInterpolator_interpolatedListOrder(t *testing.T) {
   692  	state := &State{
   693  		Modules: []*ModuleState{
   694  			&ModuleState{
   695  				Path: rootModulePath,
   696  				Resources: map[string]*ResourceState{
   697  					"aws_route53_zone.list": &ResourceState{
   698  						Type:         "aws_route53_zone",
   699  						Dependencies: []string{},
   700  						Primary: &InstanceState{
   701  							ID: "null",
   702  							Attributes: map[string]string{
   703  								"foo.#":  "12",
   704  								"foo.0":  "a",
   705  								"foo.1":  "b",
   706  								"foo.2":  "c",
   707  								"foo.3":  "d",
   708  								"foo.4":  "e",
   709  								"foo.5":  "f",
   710  								"foo.6":  "g",
   711  								"foo.7":  "h",
   712  								"foo.8":  "i",
   713  								"foo.9":  "j",
   714  								"foo.10": "k",
   715  								"foo.11": "l",
   716  							},
   717  						},
   718  					},
   719  				},
   720  			},
   721  		},
   722  	}
   723  
   724  	i := &Interpolater{
   725  		Module:    testModule(t, "interpolate-multi-vars"),
   726  		StateLock: new(sync.RWMutex),
   727  		State:     state,
   728  	}
   729  
   730  	scope := &InterpolationScope{
   731  		Path: rootModulePath,
   732  	}
   733  
   734  	list := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}
   735  
   736  	testInterpolate(t, i, scope, "aws_route53_zone.list.foo",
   737  		interfaceToVariableSwallowError(list))
   738  }
   739  
   740  func getInterpolaterFixture(t *testing.T) *Interpolater {
   741  	lock := new(sync.RWMutex)
   742  	state := &State{
   743  		Modules: []*ModuleState{
   744  			&ModuleState{
   745  				Path: rootModulePath,
   746  				Resources: map[string]*ResourceState{
   747  					"aws_route53_zone.terra.0": &ResourceState{
   748  						Type:         "aws_route53_zone",
   749  						Dependencies: []string{},
   750  						Primary: &InstanceState{
   751  							ID: "AAABBBCCCDDDEEE",
   752  							Attributes: map[string]string{
   753  								"name_servers.#": "4",
   754  								"name_servers.0": "ns-1334.awsdns-38.org",
   755  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   756  								"name_servers.2": "ns-498.awsdns-62.com",
   757  								"name_servers.3": "ns-601.awsdns-11.net",
   758  								"listeners.#":    "1",
   759  								"listeners.0":    "red",
   760  								"tags.%":         "1",
   761  								"tags.Name":      "reindeer",
   762  								"nothing.#":      "0",
   763  							},
   764  						},
   765  					},
   766  					"aws_route53_zone.terra.1": &ResourceState{
   767  						Type:         "aws_route53_zone",
   768  						Dependencies: []string{},
   769  						Primary: &InstanceState{
   770  							ID: "EEEFFFGGGHHHIII",
   771  							Attributes: map[string]string{
   772  								"name_servers.#": "4",
   773  								"name_servers.0": "ns-000.awsdns-38.org",
   774  								"name_servers.1": "ns-444.awsdns-18.co.uk",
   775  								"name_servers.2": "ns-999.awsdns-62.com",
   776  								"name_servers.3": "ns-666.awsdns-11.net",
   777  								"listeners.#":    "1",
   778  								"listeners.0":    "blue",
   779  								"special.#":      "1",
   780  								"special.0":      "extra",
   781  								"tags.%":         "1",
   782  								"tags.Name":      "white-hart",
   783  								"nothing.#":      "0",
   784  							},
   785  						},
   786  					},
   787  				},
   788  			},
   789  		},
   790  	}
   791  
   792  	return &Interpolater{
   793  		Module:    testModule(t, "interpolate-multi-vars"),
   794  		StateLock: lock,
   795  		State:     state,
   796  	}
   797  }
   798  
   799  func testInterpolate(
   800  	t *testing.T, i *Interpolater,
   801  	scope *InterpolationScope,
   802  	n string, expectedVar ast.Variable) {
   803  	v, err := config.NewInterpolatedVariable(n)
   804  	if err != nil {
   805  		t.Fatalf("err: %s", err)
   806  	}
   807  
   808  	actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
   809  		"foo": v,
   810  	})
   811  	if err != nil {
   812  		t.Fatalf("err: %s", err)
   813  	}
   814  
   815  	expected := map[string]ast.Variable{
   816  		"foo": expectedVar,
   817  	}
   818  	if !reflect.DeepEqual(actual, expected) {
   819  		spew.Config.DisableMethods = true
   820  		t.Fatalf("%q:\n\n  actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected,
   821  			spew.Sdump(actual), spew.Sdump(expected))
   822  	}
   823  }
   824  
   825  func testInterpolateErr(
   826  	t *testing.T, i *Interpolater,
   827  	scope *InterpolationScope,
   828  	n string) {
   829  	v, err := config.NewInterpolatedVariable(n)
   830  	if err != nil {
   831  		t.Fatalf("err: %s", err)
   832  	}
   833  
   834  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
   835  		"foo": v,
   836  	})
   837  	if err == nil {
   838  		t.Fatalf("%q: succeeded, but wanted error", n)
   839  	}
   840  }