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