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