github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/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_countIndex(t *testing.T) {
    17  	i := &Interpolater{}
    18  
    19  	scope := &InterpolationScope{
    20  		Path:     rootModulePath,
    21  		Resource: &Resource{CountIndex: 42},
    22  	}
    23  
    24  	testInterpolate(t, i, scope, "count.index", ast.Variable{
    25  		Value: 42,
    26  		Type:  ast.TypeInt,
    27  	})
    28  }
    29  
    30  func TestInterpolater_countIndexInWrongContext(t *testing.T) {
    31  	i := &Interpolater{}
    32  
    33  	scope := &InterpolationScope{
    34  		Path: rootModulePath,
    35  	}
    36  
    37  	n := "count.index"
    38  
    39  	v, err := config.NewInterpolatedVariable(n)
    40  	if err != nil {
    41  		t.Fatalf("err: %s", err)
    42  	}
    43  
    44  	expectedErr := fmt.Errorf("foo: count.index is only valid within resources")
    45  
    46  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
    47  		"foo": v,
    48  	})
    49  
    50  	if !reflect.DeepEqual(expectedErr, err) {
    51  		t.Fatalf("expected: %#v, got %#v", expectedErr, err)
    52  	}
    53  }
    54  
    55  func TestInterpolater_moduleVariable(t *testing.T) {
    56  	lock := new(sync.RWMutex)
    57  	state := &State{
    58  		Modules: []*ModuleState{
    59  			&ModuleState{
    60  				Path: rootModulePath,
    61  				Resources: map[string]*ResourceState{
    62  					"aws_instance.web": &ResourceState{
    63  						Type: "aws_instance",
    64  						Primary: &InstanceState{
    65  							ID: "bar",
    66  						},
    67  					},
    68  				},
    69  			},
    70  			&ModuleState{
    71  				Path: []string{RootModuleName, "child"},
    72  				Outputs: map[string]*OutputState{
    73  					"foo": &OutputState{
    74  						Type:  "string",
    75  						Value: "bar",
    76  					},
    77  				},
    78  			},
    79  		},
    80  	}
    81  
    82  	i := &Interpolater{
    83  		State:     state,
    84  		StateLock: lock,
    85  	}
    86  
    87  	scope := &InterpolationScope{
    88  		Path: rootModulePath,
    89  	}
    90  
    91  	testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
    92  		Value: "bar",
    93  		Type:  ast.TypeString,
    94  	})
    95  }
    96  
    97  func TestInterpolater_pathCwd(t *testing.T) {
    98  	i := &Interpolater{}
    99  	scope := &InterpolationScope{}
   100  
   101  	expected, err := os.Getwd()
   102  	if err != nil {
   103  		t.Fatalf("err: %s", err)
   104  	}
   105  
   106  	testInterpolate(t, i, scope, "path.cwd", ast.Variable{
   107  		Value: expected,
   108  		Type:  ast.TypeString,
   109  	})
   110  }
   111  
   112  func TestInterpolater_pathModule(t *testing.T) {
   113  	mod := testModule(t, "interpolate-path-module")
   114  	i := &Interpolater{
   115  		Module: mod,
   116  	}
   117  	scope := &InterpolationScope{
   118  		Path: []string{RootModuleName, "child"},
   119  	}
   120  
   121  	path := mod.Child([]string{"child"}).Config().Dir
   122  	testInterpolate(t, i, scope, "path.module", ast.Variable{
   123  		Value: path,
   124  		Type:  ast.TypeString,
   125  	})
   126  }
   127  
   128  func TestInterpolater_pathRoot(t *testing.T) {
   129  	mod := testModule(t, "interpolate-path-module")
   130  	i := &Interpolater{
   131  		Module: mod,
   132  	}
   133  	scope := &InterpolationScope{
   134  		Path: []string{RootModuleName, "child"},
   135  	}
   136  
   137  	path := mod.Config().Dir
   138  	testInterpolate(t, i, scope, "path.root", ast.Variable{
   139  		Value: path,
   140  		Type:  ast.TypeString,
   141  	})
   142  }
   143  
   144  func TestInterpolater_resourceVariableMap(t *testing.T) {
   145  	lock := new(sync.RWMutex)
   146  	state := &State{
   147  		Modules: []*ModuleState{
   148  			&ModuleState{
   149  				Path: rootModulePath,
   150  				Resources: map[string]*ResourceState{
   151  					"aws_instance.web": &ResourceState{
   152  						Type: "aws_instance",
   153  						Primary: &InstanceState{
   154  							ID: "bar",
   155  							Attributes: map[string]string{
   156  								"amap.%":    "3",
   157  								"amap.key1": "value1",
   158  								"amap.key2": "value2",
   159  								"amap.key3": "value3",
   160  							},
   161  						},
   162  					},
   163  				},
   164  			},
   165  		},
   166  	}
   167  
   168  	i := &Interpolater{
   169  		Module:    testModule(t, "interpolate-resource-variable"),
   170  		State:     state,
   171  		StateLock: lock,
   172  	}
   173  
   174  	scope := &InterpolationScope{
   175  		Path: rootModulePath,
   176  	}
   177  
   178  	expected := map[string]interface{}{
   179  		"key1": "value1",
   180  		"key2": "value2",
   181  		"key3": "value3",
   182  	}
   183  
   184  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   185  		interfaceToVariableSwallowError(expected))
   186  }
   187  
   188  func TestInterpolater_resourceVariableComplexMap(t *testing.T) {
   189  	lock := new(sync.RWMutex)
   190  	state := &State{
   191  		Modules: []*ModuleState{
   192  			&ModuleState{
   193  				Path: rootModulePath,
   194  				Resources: map[string]*ResourceState{
   195  					"aws_instance.web": &ResourceState{
   196  						Type: "aws_instance",
   197  						Primary: &InstanceState{
   198  							ID: "bar",
   199  							Attributes: map[string]string{
   200  								"amap.%":      "2",
   201  								"amap.key1.#": "2",
   202  								"amap.key1.0": "hello",
   203  								"amap.key1.1": "world",
   204  								"amap.key2.#": "1",
   205  								"amap.key2.0": "foo",
   206  							},
   207  						},
   208  					},
   209  				},
   210  			},
   211  		},
   212  	}
   213  
   214  	i := &Interpolater{
   215  		Module:    testModule(t, "interpolate-resource-variable"),
   216  		State:     state,
   217  		StateLock: lock,
   218  	}
   219  
   220  	scope := &InterpolationScope{
   221  		Path: rootModulePath,
   222  	}
   223  
   224  	expected := map[string]interface{}{
   225  		"key1": []interface{}{"hello", "world"},
   226  		"key2": []interface{}{"foo"},
   227  	}
   228  
   229  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   230  		interfaceToVariableSwallowError(expected))
   231  }
   232  
   233  func TestInterpolater_resourceVariable(t *testing.T) {
   234  	lock := new(sync.RWMutex)
   235  	state := &State{
   236  		Modules: []*ModuleState{
   237  			&ModuleState{
   238  				Path: rootModulePath,
   239  				Resources: map[string]*ResourceState{
   240  					"aws_instance.web": &ResourceState{
   241  						Type: "aws_instance",
   242  						Primary: &InstanceState{
   243  							ID: "bar",
   244  							Attributes: map[string]string{
   245  								"foo": "bar",
   246  							},
   247  						},
   248  					},
   249  				},
   250  			},
   251  		},
   252  	}
   253  
   254  	i := &Interpolater{
   255  		Module:    testModule(t, "interpolate-resource-variable"),
   256  		State:     state,
   257  		StateLock: lock,
   258  	}
   259  
   260  	scope := &InterpolationScope{
   261  		Path: rootModulePath,
   262  	}
   263  
   264  	testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   265  		Value: "bar",
   266  		Type:  ast.TypeString,
   267  	})
   268  }
   269  
   270  func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
   271  	// During the input walk, computed resource attributes may be entirely
   272  	// absent since we've not yet produced diffs that tell us what computed
   273  	// attributes to expect. In that case, interpolator tolerates it and
   274  	// indicates the value is computed.
   275  
   276  	lock := new(sync.RWMutex)
   277  	state := &State{
   278  		Modules: []*ModuleState{
   279  			&ModuleState{
   280  				Path:      rootModulePath,
   281  				Resources: map[string]*ResourceState{
   282  				// No resources at all yet, because we're still dealing
   283  				// with input and so the resources haven't been created.
   284  				},
   285  			},
   286  		},
   287  	}
   288  
   289  	{
   290  		i := &Interpolater{
   291  			Operation: walkInput,
   292  			Module:    testModule(t, "interpolate-resource-variable"),
   293  			State:     state,
   294  			StateLock: lock,
   295  		}
   296  
   297  		scope := &InterpolationScope{
   298  			Path: rootModulePath,
   299  		}
   300  
   301  		testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   302  			Value: config.UnknownVariableValue,
   303  			Type:  ast.TypeString,
   304  		})
   305  	}
   306  
   307  	// This doesn't apply during other walks, like plan
   308  	{
   309  		i := &Interpolater{
   310  			Operation: walkPlan,
   311  			Module:    testModule(t, "interpolate-resource-variable"),
   312  			State:     state,
   313  			StateLock: lock,
   314  		}
   315  
   316  		scope := &InterpolationScope{
   317  			Path: rootModulePath,
   318  		}
   319  
   320  		testInterpolateErr(t, i, scope, "aws_instance.web.foo")
   321  	}
   322  }
   323  
   324  func TestInterpolater_resourceVariableMulti(t *testing.T) {
   325  	lock := new(sync.RWMutex)
   326  	state := &State{
   327  		Modules: []*ModuleState{
   328  			&ModuleState{
   329  				Path: rootModulePath,
   330  				Resources: map[string]*ResourceState{
   331  					"aws_instance.web": &ResourceState{
   332  						Type: "aws_instance",
   333  						Primary: &InstanceState{
   334  							ID: "bar",
   335  							Attributes: map[string]string{
   336  								"foo": config.UnknownVariableValue,
   337  							},
   338  						},
   339  					},
   340  				},
   341  			},
   342  		},
   343  	}
   344  
   345  	i := &Interpolater{
   346  		Module:    testModule(t, "interpolate-resource-variable"),
   347  		State:     state,
   348  		StateLock: lock,
   349  	}
   350  
   351  	scope := &InterpolationScope{
   352  		Path: rootModulePath,
   353  	}
   354  
   355  	testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
   356  		Value: config.UnknownVariableValue,
   357  		Type:  ast.TypeString,
   358  	})
   359  }
   360  
   361  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   362  	variable, _ := hil.InterfaceToVariable(input)
   363  	return variable
   364  }
   365  
   366  func TestInterpolator_resourceMultiAttributes(t *testing.T) {
   367  	lock := new(sync.RWMutex)
   368  	state := &State{
   369  		Modules: []*ModuleState{
   370  			{
   371  				Path: rootModulePath,
   372  				Resources: map[string]*ResourceState{
   373  					"aws_route53_zone.yada": {
   374  						Type:         "aws_route53_zone",
   375  						Dependencies: []string{},
   376  						Primary: &InstanceState{
   377  							ID: "AAABBBCCCDDDEEE",
   378  							Attributes: map[string]string{
   379  								"name_servers.#": "4",
   380  								"name_servers.0": "ns-1334.awsdns-38.org",
   381  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   382  								"name_servers.2": "ns-498.awsdns-62.com",
   383  								"name_servers.3": "ns-601.awsdns-11.net",
   384  								"listeners.#":    "1",
   385  								"listeners.0":    "red",
   386  								"tags.%":         "1",
   387  								"tags.Name":      "reindeer",
   388  								"nothing.#":      "0",
   389  							},
   390  						},
   391  					},
   392  				},
   393  			},
   394  		},
   395  	}
   396  
   397  	i := &Interpolater{
   398  		Module:    testModule(t, "interpolate-multi-vars"),
   399  		StateLock: lock,
   400  		State:     state,
   401  	}
   402  
   403  	scope := &InterpolationScope{
   404  		Path: rootModulePath,
   405  	}
   406  
   407  	name_servers := []interface{}{
   408  		"ns-1334.awsdns-38.org",
   409  		"ns-1680.awsdns-18.co.uk",
   410  		"ns-498.awsdns-62.com",
   411  		"ns-601.awsdns-11.net",
   412  	}
   413  
   414  	// More than 1 element
   415  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers",
   416  		interfaceToVariableSwallowError(name_servers))
   417  
   418  	// Exactly 1 element
   419  	testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners",
   420  		interfaceToVariableSwallowError([]interface{}{"red"}))
   421  
   422  	// Zero elements
   423  	testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing",
   424  		interfaceToVariableSwallowError([]interface{}{}))
   425  
   426  	// Maps still need to work
   427  	testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
   428  		Value: "reindeer",
   429  		Type:  ast.TypeString,
   430  	})
   431  }
   432  
   433  func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
   434  	i := getInterpolaterFixture(t)
   435  	scope := &InterpolationScope{
   436  		Path: rootModulePath,
   437  	}
   438  
   439  	name_servers := []interface{}{
   440  		"ns-1334.awsdns-38.org",
   441  		"ns-1680.awsdns-18.co.uk",
   442  		"ns-498.awsdns-62.com",
   443  		"ns-601.awsdns-11.net",
   444  		"ns-000.awsdns-38.org",
   445  		"ns-444.awsdns-18.co.uk",
   446  		"ns-999.awsdns-62.com",
   447  		"ns-666.awsdns-11.net",
   448  	}
   449  
   450  	// More than 1 element
   451  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers",
   452  		interfaceToVariableSwallowError(name_servers[0:4]))
   453  
   454  	// More than 1 element in both
   455  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers",
   456  		interfaceToVariableSwallowError(name_servers))
   457  
   458  	// Exactly 1 element
   459  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners",
   460  		interfaceToVariableSwallowError([]interface{}{"red"}))
   461  
   462  	// Exactly 1 element in both
   463  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners",
   464  		interfaceToVariableSwallowError([]interface{}{"red", "blue"}))
   465  
   466  	// Zero elements
   467  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing",
   468  		interfaceToVariableSwallowError([]interface{}{}))
   469  
   470  	// Zero + 1 element
   471  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special",
   472  		interfaceToVariableSwallowError([]interface{}{"extra"}))
   473  
   474  	// Maps still need to work
   475  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
   476  		Value: "reindeer",
   477  		Type:  ast.TypeString,
   478  	})
   479  
   480  	// Maps still need to work in both
   481  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name",
   482  		interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"}))
   483  }
   484  
   485  func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
   486  	lock := new(sync.RWMutex)
   487  	// The state would never be written with an UnknownVariableValue in it, but
   488  	// it can/does exist that way in memory during the plan phase.
   489  	state := &State{
   490  		Modules: []*ModuleState{
   491  			&ModuleState{
   492  				Path: rootModulePath,
   493  				Resources: map[string]*ResourceState{
   494  					"aws_route53_zone.yada": &ResourceState{
   495  						Type: "aws_route53_zone",
   496  						Primary: &InstanceState{
   497  							ID: "z-abc123",
   498  							Attributes: map[string]string{
   499  								"name_servers.#": config.UnknownVariableValue,
   500  							},
   501  						},
   502  					},
   503  				},
   504  			},
   505  		},
   506  	}
   507  	i := &Interpolater{
   508  		Module:    testModule(t, "interpolate-multi-vars"),
   509  		StateLock: lock,
   510  		State:     state,
   511  	}
   512  
   513  	scope := &InterpolationScope{
   514  		Path: rootModulePath,
   515  	}
   516  
   517  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
   518  		Value: config.UnknownVariableValue,
   519  		Type:  ast.TypeString,
   520  	})
   521  }
   522  
   523  func TestInterpolater_selfVarWithoutResource(t *testing.T) {
   524  	i := &Interpolater{}
   525  
   526  	scope := &InterpolationScope{
   527  		Path: rootModulePath,
   528  	}
   529  
   530  	v, err := config.NewInterpolatedVariable("self.name")
   531  	if err != nil {
   532  		t.Fatalf("err: %s", err)
   533  	}
   534  
   535  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
   536  	if err == nil {
   537  		t.Fatalf("expected err, got none")
   538  	}
   539  }
   540  
   541  func getInterpolaterFixture(t *testing.T) *Interpolater {
   542  	lock := new(sync.RWMutex)
   543  	state := &State{
   544  		Modules: []*ModuleState{
   545  			&ModuleState{
   546  				Path: rootModulePath,
   547  				Resources: map[string]*ResourceState{
   548  					"aws_route53_zone.terra.0": &ResourceState{
   549  						Type:         "aws_route53_zone",
   550  						Dependencies: []string{},
   551  						Primary: &InstanceState{
   552  							ID: "AAABBBCCCDDDEEE",
   553  							Attributes: map[string]string{
   554  								"name_servers.#": "4",
   555  								"name_servers.0": "ns-1334.awsdns-38.org",
   556  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   557  								"name_servers.2": "ns-498.awsdns-62.com",
   558  								"name_servers.3": "ns-601.awsdns-11.net",
   559  								"listeners.#":    "1",
   560  								"listeners.0":    "red",
   561  								"tags.%":         "1",
   562  								"tags.Name":      "reindeer",
   563  								"nothing.#":      "0",
   564  							},
   565  						},
   566  					},
   567  					"aws_route53_zone.terra.1": &ResourceState{
   568  						Type:         "aws_route53_zone",
   569  						Dependencies: []string{},
   570  						Primary: &InstanceState{
   571  							ID: "EEEFFFGGGHHHIII",
   572  							Attributes: map[string]string{
   573  								"name_servers.#": "4",
   574  								"name_servers.0": "ns-000.awsdns-38.org",
   575  								"name_servers.1": "ns-444.awsdns-18.co.uk",
   576  								"name_servers.2": "ns-999.awsdns-62.com",
   577  								"name_servers.3": "ns-666.awsdns-11.net",
   578  								"listeners.#":    "1",
   579  								"listeners.0":    "blue",
   580  								"special.#":      "1",
   581  								"special.0":      "extra",
   582  								"tags.%":         "1",
   583  								"tags.Name":      "white-hart",
   584  								"nothing.#":      "0",
   585  							},
   586  						},
   587  					},
   588  				},
   589  			},
   590  		},
   591  	}
   592  
   593  	return &Interpolater{
   594  		Module:    testModule(t, "interpolate-multi-vars"),
   595  		StateLock: lock,
   596  		State:     state,
   597  	}
   598  }
   599  
   600  func testInterpolate(
   601  	t *testing.T, i *Interpolater,
   602  	scope *InterpolationScope,
   603  	n string, expectedVar ast.Variable) {
   604  	v, err := config.NewInterpolatedVariable(n)
   605  	if err != nil {
   606  		t.Fatalf("err: %s", err)
   607  	}
   608  
   609  	actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
   610  		"foo": v,
   611  	})
   612  	if err != nil {
   613  		t.Fatalf("err: %s", err)
   614  	}
   615  
   616  	expected := map[string]ast.Variable{
   617  		"foo": expectedVar,
   618  	}
   619  	if !reflect.DeepEqual(actual, expected) {
   620  		spew.Config.DisableMethods = true
   621  		t.Fatalf("%q:\n\n  actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected,
   622  			spew.Sdump(actual), spew.Sdump(expected))
   623  	}
   624  }
   625  
   626  func testInterpolateErr(
   627  	t *testing.T, i *Interpolater,
   628  	scope *InterpolationScope,
   629  	n string) {
   630  	v, err := config.NewInterpolatedVariable(n)
   631  	if err != nil {
   632  		t.Fatalf("err: %s", err)
   633  	}
   634  
   635  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
   636  		"foo": v,
   637  	})
   638  	if err == nil {
   639  		t.Fatalf("%q: succeeded, but wanted error", n)
   640  	}
   641  }