github.com/mehmetalisavas/terraform@v0.7.10/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 TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) {
   362  	lock := new(sync.RWMutex)
   363  	state := &State{
   364  		Modules: []*ModuleState{
   365  			&ModuleState{
   366  				Path: rootModulePath,
   367  				Resources: map[string]*ResourceState{
   368  					"aws_instance.web.0": &ResourceState{
   369  						Type: "aws_instance",
   370  						Primary: &InstanceState{
   371  							ID:         "a",
   372  							Attributes: map[string]string{"foo": "a"},
   373  						},
   374  					},
   375  
   376  					"aws_instance.web.1": &ResourceState{
   377  						Type: "aws_instance",
   378  						Primary: &InstanceState{
   379  							ID:         "b",
   380  							Attributes: map[string]string{"foo": "b"},
   381  						},
   382  					},
   383  				},
   384  			},
   385  		},
   386  	}
   387  
   388  	i := &Interpolater{
   389  		Operation: walkApply,
   390  		Module:    testModule(t, "interpolate-multi-interp"),
   391  		State:     state,
   392  		StateLock: lock,
   393  	}
   394  
   395  	scope := &InterpolationScope{
   396  		Path: rootModulePath,
   397  	}
   398  
   399  	expected := []interface{}{"a", "b"}
   400  	testInterpolate(t, i, scope, "aws_instance.web.*.foo",
   401  		interfaceToVariableSwallowError(expected))
   402  }
   403  
   404  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   405  	variable, _ := hil.InterfaceToVariable(input)
   406  	return variable
   407  }
   408  
   409  func TestInterpolator_resourceMultiAttributes(t *testing.T) {
   410  	lock := new(sync.RWMutex)
   411  	state := &State{
   412  		Modules: []*ModuleState{
   413  			{
   414  				Path: rootModulePath,
   415  				Resources: map[string]*ResourceState{
   416  					"aws_route53_zone.yada": {
   417  						Type:         "aws_route53_zone",
   418  						Dependencies: []string{},
   419  						Primary: &InstanceState{
   420  							ID: "AAABBBCCCDDDEEE",
   421  							Attributes: map[string]string{
   422  								"name_servers.#": "4",
   423  								"name_servers.0": "ns-1334.awsdns-38.org",
   424  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   425  								"name_servers.2": "ns-498.awsdns-62.com",
   426  								"name_servers.3": "ns-601.awsdns-11.net",
   427  								"listeners.#":    "1",
   428  								"listeners.0":    "red",
   429  								"tags.%":         "1",
   430  								"tags.Name":      "reindeer",
   431  								"nothing.#":      "0",
   432  							},
   433  						},
   434  					},
   435  				},
   436  			},
   437  		},
   438  	}
   439  
   440  	i := &Interpolater{
   441  		Module:    testModule(t, "interpolate-multi-vars"),
   442  		StateLock: lock,
   443  		State:     state,
   444  	}
   445  
   446  	scope := &InterpolationScope{
   447  		Path: rootModulePath,
   448  	}
   449  
   450  	name_servers := []interface{}{
   451  		"ns-1334.awsdns-38.org",
   452  		"ns-1680.awsdns-18.co.uk",
   453  		"ns-498.awsdns-62.com",
   454  		"ns-601.awsdns-11.net",
   455  	}
   456  
   457  	// More than 1 element
   458  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers",
   459  		interfaceToVariableSwallowError(name_servers))
   460  
   461  	// Exactly 1 element
   462  	testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners",
   463  		interfaceToVariableSwallowError([]interface{}{"red"}))
   464  
   465  	// Zero elements
   466  	testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing",
   467  		interfaceToVariableSwallowError([]interface{}{}))
   468  
   469  	// Maps still need to work
   470  	testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
   471  		Value: "reindeer",
   472  		Type:  ast.TypeString,
   473  	})
   474  }
   475  
   476  func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
   477  	i := getInterpolaterFixture(t)
   478  	scope := &InterpolationScope{
   479  		Path: rootModulePath,
   480  	}
   481  
   482  	name_servers := []interface{}{
   483  		"ns-1334.awsdns-38.org",
   484  		"ns-1680.awsdns-18.co.uk",
   485  		"ns-498.awsdns-62.com",
   486  		"ns-601.awsdns-11.net",
   487  		"ns-000.awsdns-38.org",
   488  		"ns-444.awsdns-18.co.uk",
   489  		"ns-999.awsdns-62.com",
   490  		"ns-666.awsdns-11.net",
   491  	}
   492  
   493  	// More than 1 element
   494  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers",
   495  		interfaceToVariableSwallowError(name_servers[:4]))
   496  
   497  	// More than 1 element in both
   498  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers",
   499  		interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]}))
   500  
   501  	// Exactly 1 element
   502  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners",
   503  		interfaceToVariableSwallowError([]interface{}{"red"}))
   504  
   505  	// Exactly 1 element in both
   506  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners",
   507  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}}))
   508  
   509  	// Zero elements
   510  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing",
   511  		interfaceToVariableSwallowError([]interface{}{}))
   512  
   513  	// Zero + 1 element
   514  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special",
   515  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}}))
   516  
   517  	// Maps still need to work
   518  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
   519  		Value: "reindeer",
   520  		Type:  ast.TypeString,
   521  	})
   522  
   523  	// Maps still need to work in both
   524  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name",
   525  		interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"}))
   526  }
   527  
   528  func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
   529  	lock := new(sync.RWMutex)
   530  	// The state would never be written with an UnknownVariableValue in it, but
   531  	// it can/does exist that way in memory during the plan phase.
   532  	state := &State{
   533  		Modules: []*ModuleState{
   534  			&ModuleState{
   535  				Path: rootModulePath,
   536  				Resources: map[string]*ResourceState{
   537  					"aws_route53_zone.yada": &ResourceState{
   538  						Type: "aws_route53_zone",
   539  						Primary: &InstanceState{
   540  							ID: "z-abc123",
   541  							Attributes: map[string]string{
   542  								"name_servers.#": config.UnknownVariableValue,
   543  							},
   544  						},
   545  					},
   546  				},
   547  			},
   548  		},
   549  	}
   550  	i := &Interpolater{
   551  		Module:    testModule(t, "interpolate-multi-vars"),
   552  		StateLock: lock,
   553  		State:     state,
   554  	}
   555  
   556  	scope := &InterpolationScope{
   557  		Path: rootModulePath,
   558  	}
   559  
   560  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
   561  		Value: config.UnknownVariableValue,
   562  		Type:  ast.TypeString,
   563  	})
   564  }
   565  
   566  func TestInterpolater_selfVarWithoutResource(t *testing.T) {
   567  	i := &Interpolater{}
   568  
   569  	scope := &InterpolationScope{
   570  		Path: rootModulePath,
   571  	}
   572  
   573  	v, err := config.NewInterpolatedVariable("self.name")
   574  	if err != nil {
   575  		t.Fatalf("err: %s", err)
   576  	}
   577  
   578  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
   579  	if err == nil {
   580  		t.Fatalf("expected err, got none")
   581  	}
   582  }
   583  
   584  func getInterpolaterFixture(t *testing.T) *Interpolater {
   585  	lock := new(sync.RWMutex)
   586  	state := &State{
   587  		Modules: []*ModuleState{
   588  			&ModuleState{
   589  				Path: rootModulePath,
   590  				Resources: map[string]*ResourceState{
   591  					"aws_route53_zone.terra.0": &ResourceState{
   592  						Type:         "aws_route53_zone",
   593  						Dependencies: []string{},
   594  						Primary: &InstanceState{
   595  							ID: "AAABBBCCCDDDEEE",
   596  							Attributes: map[string]string{
   597  								"name_servers.#": "4",
   598  								"name_servers.0": "ns-1334.awsdns-38.org",
   599  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   600  								"name_servers.2": "ns-498.awsdns-62.com",
   601  								"name_servers.3": "ns-601.awsdns-11.net",
   602  								"listeners.#":    "1",
   603  								"listeners.0":    "red",
   604  								"tags.%":         "1",
   605  								"tags.Name":      "reindeer",
   606  								"nothing.#":      "0",
   607  							},
   608  						},
   609  					},
   610  					"aws_route53_zone.terra.1": &ResourceState{
   611  						Type:         "aws_route53_zone",
   612  						Dependencies: []string{},
   613  						Primary: &InstanceState{
   614  							ID: "EEEFFFGGGHHHIII",
   615  							Attributes: map[string]string{
   616  								"name_servers.#": "4",
   617  								"name_servers.0": "ns-000.awsdns-38.org",
   618  								"name_servers.1": "ns-444.awsdns-18.co.uk",
   619  								"name_servers.2": "ns-999.awsdns-62.com",
   620  								"name_servers.3": "ns-666.awsdns-11.net",
   621  								"listeners.#":    "1",
   622  								"listeners.0":    "blue",
   623  								"special.#":      "1",
   624  								"special.0":      "extra",
   625  								"tags.%":         "1",
   626  								"tags.Name":      "white-hart",
   627  								"nothing.#":      "0",
   628  							},
   629  						},
   630  					},
   631  				},
   632  			},
   633  		},
   634  	}
   635  
   636  	return &Interpolater{
   637  		Module:    testModule(t, "interpolate-multi-vars"),
   638  		StateLock: lock,
   639  		State:     state,
   640  	}
   641  }
   642  
   643  func testInterpolate(
   644  	t *testing.T, i *Interpolater,
   645  	scope *InterpolationScope,
   646  	n string, expectedVar ast.Variable) {
   647  	v, err := config.NewInterpolatedVariable(n)
   648  	if err != nil {
   649  		t.Fatalf("err: %s", err)
   650  	}
   651  
   652  	actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
   653  		"foo": v,
   654  	})
   655  	if err != nil {
   656  		t.Fatalf("err: %s", err)
   657  	}
   658  
   659  	expected := map[string]ast.Variable{
   660  		"foo": expectedVar,
   661  	}
   662  	if !reflect.DeepEqual(actual, expected) {
   663  		spew.Config.DisableMethods = true
   664  		t.Fatalf("%q:\n\n  actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected,
   665  			spew.Sdump(actual), spew.Sdump(expected))
   666  	}
   667  }
   668  
   669  func testInterpolateErr(
   670  	t *testing.T, i *Interpolater,
   671  	scope *InterpolationScope,
   672  	n string) {
   673  	v, err := config.NewInterpolatedVariable(n)
   674  	if err != nil {
   675  		t.Fatalf("err: %s", err)
   676  	}
   677  
   678  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
   679  		"foo": v,
   680  	})
   681  	if err == nil {
   682  		t.Fatalf("%q: succeeded, but wanted error", n)
   683  	}
   684  }