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