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