2015-02-05 18:47:06 -06:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-07-05 09:26:01 -05:00
|
|
|
"log"
|
2015-02-05 18:47:06 -06:00
|
|
|
"os"
|
2015-07-05 09:26:01 -05:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
2016-11-04 13:07:01 -05:00
|
|
|
"strconv"
|
2015-02-05 18:47:06 -06:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2016-04-11 12:40:06 -05:00
|
|
|
"github.com/hashicorp/hil"
|
2016-01-31 01:43:41 -06:00
|
|
|
"github.com/hashicorp/hil/ast"
|
2015-02-05 18:47:06 -06:00
|
|
|
"github.com/hashicorp/terraform/config"
|
|
|
|
"github.com/hashicorp/terraform/config/module"
|
2016-06-11 06:27:39 -05:00
|
|
|
"github.com/hashicorp/terraform/flatmap"
|
2015-02-05 18:47:06 -06:00
|
|
|
)
|
|
|
|
|
2015-04-21 23:31:53 -05:00
|
|
|
const (
|
|
|
|
// VarEnvPrefix is the prefix of variables that are read from
|
|
|
|
// the environment to set variables here.
|
|
|
|
VarEnvPrefix = "TF_VAR_"
|
|
|
|
)
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
// Interpolater is the structure responsible for determining the values
|
|
|
|
// for interpolations such as `aws_instance.foo.bar`.
|
|
|
|
type Interpolater struct {
|
2016-04-11 12:40:06 -05:00
|
|
|
Operation walkOperation
|
|
|
|
Module *module.Tree
|
|
|
|
State *State
|
|
|
|
StateLock *sync.RWMutex
|
|
|
|
VariableValues map[string]interface{}
|
|
|
|
VariableValuesLock *sync.Mutex
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// InterpolationScope is the current scope of execution. This is required
|
|
|
|
// since some variables which are interpolated are dependent on what we're
|
|
|
|
// operating on and where we are.
|
|
|
|
type InterpolationScope struct {
|
|
|
|
Path []string
|
|
|
|
Resource *Resource
|
|
|
|
}
|
|
|
|
|
|
|
|
// Values returns the values for all the variables in the given map.
|
|
|
|
func (i *Interpolater) Values(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) {
|
|
|
|
result := make(map[string]ast.Variable, len(vars))
|
2015-02-05 18:53:50 -06:00
|
|
|
|
|
|
|
// Copy the default variables
|
|
|
|
if i.Module != nil && scope != nil {
|
|
|
|
mod := i.Module
|
|
|
|
if len(scope.Path) > 1 {
|
|
|
|
mod = i.Module.Child(scope.Path[1:])
|
|
|
|
}
|
|
|
|
for _, v := range mod.Config().Variables {
|
2016-04-11 12:40:06 -05:00
|
|
|
// Set default variables
|
|
|
|
if v.Default == nil {
|
|
|
|
continue
|
2015-02-05 18:53:50 -06:00
|
|
|
}
|
2016-04-11 12:40:06 -05:00
|
|
|
|
|
|
|
n := fmt.Sprintf("var.%s", v.Name)
|
|
|
|
variable, err := hil.InterfaceToVariable(v.Default)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid default map value for %s: %v", v.Name, v.Default)
|
|
|
|
}
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
|
2016-04-11 12:40:06 -05:00
|
|
|
result[n] = variable
|
2015-02-05 18:53:50 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
for n, rawV := range vars {
|
|
|
|
var err error
|
|
|
|
switch v := rawV.(type) {
|
|
|
|
case *config.CountVariable:
|
|
|
|
err = i.valueCountVar(scope, n, v, result)
|
|
|
|
case *config.ModuleVariable:
|
|
|
|
err = i.valueModuleVar(scope, n, v, result)
|
|
|
|
case *config.PathVariable:
|
|
|
|
err = i.valuePathVar(scope, n, v, result)
|
|
|
|
case *config.ResourceVariable:
|
|
|
|
err = i.valueResourceVar(scope, n, v, result)
|
2015-02-23 16:56:02 -06:00
|
|
|
case *config.SelfVariable:
|
|
|
|
err = i.valueSelfVar(scope, n, v, result)
|
2015-11-13 11:07:02 -06:00
|
|
|
case *config.SimpleVariable:
|
|
|
|
err = i.valueSimpleVar(scope, n, v, result)
|
2015-02-05 18:47:06 -06:00
|
|
|
case *config.UserVariable:
|
|
|
|
err = i.valueUserVar(scope, n, v, result)
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("%s: unknown variable type: %T", n, rawV)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interpolater) valueCountVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.CountVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
|
|
|
switch v.Type {
|
|
|
|
case config.CountValueIndex:
|
2015-04-14 19:42:23 -05:00
|
|
|
if scope.Resource == nil {
|
|
|
|
return fmt.Errorf("%s: count.index is only valid within resources", n)
|
|
|
|
}
|
2015-02-05 18:47:06 -06:00
|
|
|
result[n] = ast.Variable{
|
|
|
|
Value: scope.Resource.CountIndex,
|
|
|
|
Type: ast.TypeInt,
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("%s: unknown count type: %#v", n, v.Type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-22 09:22:33 -05:00
|
|
|
func unknownVariable() ast.Variable {
|
|
|
|
return ast.Variable{
|
2016-10-26 23:00:24 -05:00
|
|
|
Type: ast.TypeUnknown,
|
2016-03-22 09:22:33 -05:00
|
|
|
Value: config.UnknownVariableValue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-08 20:01:06 -06:00
|
|
|
func unknownValue() string {
|
|
|
|
return hil.UnknownValue
|
|
|
|
}
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
func (i *Interpolater) valueModuleVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.ModuleVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
2016-05-19 12:46:51 -05:00
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
// Build the path to the child module we want
|
|
|
|
path := make([]string, len(scope.Path), len(scope.Path)+1)
|
|
|
|
copy(path, scope.Path)
|
|
|
|
path = append(path, v.Name)
|
|
|
|
|
|
|
|
// Grab the lock so that if other interpolations are running or
|
|
|
|
// state is being modified, we'll be safe.
|
|
|
|
i.StateLock.RLock()
|
|
|
|
defer i.StateLock.RUnlock()
|
|
|
|
|
|
|
|
// Get the module where we're looking for the value
|
|
|
|
mod := i.State.ModuleByPath(path)
|
|
|
|
if mod == nil {
|
|
|
|
// If the module doesn't exist, then we can return an empty string.
|
|
|
|
// This happens usually only in Refresh() when we haven't populated
|
|
|
|
// a state. During validation, we semantically verify that all
|
|
|
|
// modules reference other modules, and graph ordering should
|
|
|
|
// ensure that the module is in the state, so if we reach this
|
|
|
|
// point otherwise it really is a panic.
|
2016-03-22 09:22:33 -05:00
|
|
|
result[n] = unknownVariable()
|
2015-02-05 18:47:06 -06:00
|
|
|
} else {
|
|
|
|
// Get the value from the outputs
|
2016-05-11 19:05:02 -05:00
|
|
|
if outputState, ok := mod.Outputs[v.Field]; ok {
|
|
|
|
output, err := hil.InterfaceToVariable(outputState.Value)
|
2016-04-11 12:40:06 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
result[n] = output
|
2016-03-22 09:22:33 -05:00
|
|
|
} else {
|
2015-02-05 18:47:06 -06:00
|
|
|
// Same reasons as the comment above.
|
2016-03-22 09:22:33 -05:00
|
|
|
result[n] = unknownVariable()
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interpolater) valuePathVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.PathVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
|
|
|
switch v.Type {
|
|
|
|
case config.PathValueCwd:
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Couldn't get cwd for var %s: %s",
|
|
|
|
v.FullKey(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
result[n] = ast.Variable{
|
|
|
|
Value: wd,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
}
|
|
|
|
case config.PathValueModule:
|
|
|
|
if t := i.Module.Child(scope.Path[1:]); t != nil {
|
|
|
|
result[n] = ast.Variable{
|
|
|
|
Value: t.Config().Dir,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case config.PathValueRoot:
|
|
|
|
result[n] = ast.Variable{
|
|
|
|
Value: i.Module.Config().Dir,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("%s: unknown path type: %#v", n, v.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interpolater) valueResourceVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.ResourceVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
|
|
|
// If we're computing all dynamic fields, then module vars count
|
|
|
|
// and we mark it as computed.
|
2015-04-10 15:51:22 -05:00
|
|
|
if i.Operation == walkValidate {
|
2016-10-26 23:00:24 -05:00
|
|
|
result[n] = unknownVariable()
|
2015-02-05 18:47:06 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-05-14 11:25:03 -05:00
|
|
|
var variable *ast.Variable
|
|
|
|
var err error
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
if v.Multi && v.Index == -1 {
|
2016-05-14 11:25:03 -05:00
|
|
|
variable, err = i.computeResourceMultiVariable(scope, v)
|
2015-02-05 18:47:06 -06:00
|
|
|
} else {
|
2016-05-14 11:25:03 -05:00
|
|
|
variable, err = i.computeResourceVariable(scope, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if variable == nil {
|
|
|
|
// During the input walk we tolerate missing variables because
|
|
|
|
// we haven't yet had a chance to refresh state, so dynamic data may
|
|
|
|
// not yet be complete.
|
|
|
|
// If it truly is missing, we'll catch it on a later walk.
|
|
|
|
// This applies only to graph nodes that interpolate during the
|
|
|
|
// config walk, e.g. providers.
|
|
|
|
if i.Operation == walkInput {
|
2016-10-26 23:00:24 -05:00
|
|
|
result[n] = unknownVariable()
|
2016-05-14 11:25:03 -05:00
|
|
|
return nil
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
}
|
2016-05-14 11:25:03 -05:00
|
|
|
|
|
|
|
return fmt.Errorf("variable %q is nil, but no error was reported", v.Name)
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-05-14 11:25:03 -05:00
|
|
|
result[n] = *variable
|
2015-02-05 18:47:06 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-23 16:56:02 -06:00
|
|
|
func (i *Interpolater) valueSelfVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.SelfVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
2016-02-23 11:42:55 -06:00
|
|
|
if scope == nil || scope.Resource == nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"%s: invalid scope, self variables are only valid on resources", n)
|
|
|
|
}
|
2016-08-31 13:36:51 -05:00
|
|
|
|
2015-02-23 16:56:02 -06:00
|
|
|
rv, err := config.NewResourceVariable(fmt.Sprintf(
|
|
|
|
"%s.%s.%d.%s",
|
|
|
|
scope.Resource.Type,
|
|
|
|
scope.Resource.Name,
|
|
|
|
scope.Resource.CountIndex,
|
|
|
|
v.Field))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return i.valueResourceVar(scope, n, rv, result)
|
|
|
|
}
|
|
|
|
|
2015-11-13 11:07:02 -06:00
|
|
|
func (i *Interpolater) valueSimpleVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.SimpleVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
2016-10-28 14:20:25 -05:00
|
|
|
// This error message includes some information for people who
|
|
|
|
// relied on this for their template_file data sources. We should
|
|
|
|
// remove this at some point but there isn't any rush.
|
|
|
|
return fmt.Errorf(
|
|
|
|
"invalid variable syntax: %q. If this is part of inline `template` parameter\n" +
|
|
|
|
"then you must escape the interpolation with two dollar signs. For\n" +
|
|
|
|
"example: ${a} becomes $${a}." +
|
|
|
|
n)
|
2015-11-13 11:07:02 -06:00
|
|
|
}
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
func (i *Interpolater) valueUserVar(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string,
|
|
|
|
v *config.UserVariable,
|
|
|
|
result map[string]ast.Variable) error {
|
2016-04-11 12:40:06 -05:00
|
|
|
i.VariableValuesLock.Lock()
|
|
|
|
defer i.VariableValuesLock.Unlock()
|
|
|
|
val, ok := i.VariableValues[v.Name]
|
2015-02-05 18:47:06 -06:00
|
|
|
if ok {
|
2016-04-11 12:40:06 -05:00
|
|
|
varValue, err := hil.InterfaceToVariable(val)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
|
|
|
|
v.Name, val, err)
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
2016-04-11 12:40:06 -05:00
|
|
|
result[n] = varValue
|
2015-02-05 18:47:06 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := result[n]; !ok && i.Operation == walkValidate {
|
2016-04-11 12:40:06 -05:00
|
|
|
result[n] = unknownVariable()
|
2015-02-05 18:47:06 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look up if we have any variables with this prefix because
|
|
|
|
// those are map overrides. Include those.
|
2016-04-11 12:40:06 -05:00
|
|
|
for k, val := range i.VariableValues {
|
2015-02-05 18:47:06 -06:00
|
|
|
if strings.HasPrefix(k, v.Name+".") {
|
2016-04-11 12:40:06 -05:00
|
|
|
keyComponents := strings.Split(k, ".")
|
|
|
|
overrideKey := keyComponents[len(keyComponents)-1]
|
|
|
|
|
|
|
|
mapInterface, ok := result["var."+v.Name]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("override for non-existent variable: %s", v.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
mapVariable := mapInterface.Value.(map[string]ast.Variable)
|
|
|
|
|
|
|
|
varValue, err := hil.InterfaceToVariable(val)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
|
|
|
|
v.Name, val, err)
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
2016-04-11 12:40:06 -05:00
|
|
|
mapVariable[overrideKey] = varValue
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interpolater) computeResourceVariable(
|
|
|
|
scope *InterpolationScope,
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
v *config.ResourceVariable) (*ast.Variable, error) {
|
2015-02-05 18:47:06 -06:00
|
|
|
id := v.ResourceId()
|
|
|
|
if v.Multi {
|
|
|
|
id = fmt.Sprintf("%s.%d", id, v.Index)
|
|
|
|
}
|
|
|
|
|
|
|
|
i.StateLock.RLock()
|
|
|
|
defer i.StateLock.RUnlock()
|
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
unknownVariable := unknownVariable()
|
|
|
|
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
// These variables must be declared early because of the use of GOTO
|
|
|
|
var isList bool
|
|
|
|
var isMap bool
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
// Get the information about this resource variable, and verify
|
|
|
|
// that it exists and such.
|
2016-08-31 13:36:51 -05:00
|
|
|
module, cr, err := i.resourceVariableInfo(scope, v)
|
2015-02-05 18:47:06 -06:00
|
|
|
if err != nil {
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return nil, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-08-31 13:36:51 -05:00
|
|
|
// If we're requesting "count" its a special variable that we grab
|
|
|
|
// directly from the config itself.
|
|
|
|
if v.Field == "count" {
|
|
|
|
count, err := cr.Count()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"Error reading %s count: %s",
|
|
|
|
v.ResourceId(),
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ast.Variable{Type: ast.TypeInt, Value: count}, nil
|
|
|
|
}
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
// If we have no module in the state yet or count, return empty
|
|
|
|
if module == nil || len(module.Resources) == 0 {
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return nil, nil
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the resource out from the state. We know the state exists
|
|
|
|
// at this point and if there is a state, we expect there to be a
|
|
|
|
// resource with the given name.
|
|
|
|
r, ok := module.Resources[id]
|
|
|
|
if !ok && v.Multi && v.Index == 0 {
|
|
|
|
r, ok = module.Resources[v.ResourceId()]
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
r = nil
|
|
|
|
}
|
|
|
|
if r == nil {
|
2015-05-11 16:57:20 -05:00
|
|
|
goto MISSING
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if r.Primary == nil {
|
|
|
|
goto MISSING
|
|
|
|
}
|
|
|
|
|
|
|
|
if attr, ok := r.Primary.Attributes[v.Field]; ok {
|
2016-11-10 19:14:20 -06:00
|
|
|
v, err := hil.InterfaceToVariable(attr)
|
|
|
|
return &v, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-04-27 16:41:17 -05:00
|
|
|
// computed list or map attribute
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
_, isList = r.Primary.Attributes[v.Field+".#"]
|
|
|
|
_, isMap = r.Primary.Attributes[v.Field+".%"]
|
|
|
|
if isList || isMap {
|
2016-04-27 16:41:17 -05:00
|
|
|
variable, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return &variable, err
|
2015-07-05 09:26:01 -05:00
|
|
|
}
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
// At apply time, we can't do the "maybe has it" check below
|
|
|
|
// that we need for plans since parent elements might be computed.
|
|
|
|
// Therefore, it is an error and we're missing the key.
|
|
|
|
//
|
|
|
|
// TODO: test by creating a state and configuration that is referencing
|
|
|
|
// a non-existent variable "foo.bar" where the state only has "foo"
|
|
|
|
// and verify plan works, but apply doesn't.
|
2015-10-02 13:20:09 -05:00
|
|
|
if i.Operation == walkApply || i.Operation == walkDestroy {
|
2015-02-05 18:47:06 -06:00
|
|
|
goto MISSING
|
|
|
|
}
|
|
|
|
|
|
|
|
// We didn't find the exact field, so lets separate the dots
|
|
|
|
// and see if anything along the way is a computed set. i.e. if
|
|
|
|
// we have "foo.0.bar" as the field, check to see if "foo" is
|
|
|
|
// a computed list. If so, then the whole thing is computed.
|
|
|
|
if parts := strings.Split(v.Field, "."); len(parts) > 1 {
|
|
|
|
for i := 1; i < len(parts); i++ {
|
|
|
|
// Lists and sets make this
|
|
|
|
key := fmt.Sprintf("%s.#", strings.Join(parts[:i], "."))
|
|
|
|
if attr, ok := r.Primary.Attributes[key]; ok {
|
2016-11-10 22:23:28 -06:00
|
|
|
v, err := hil.InterfaceToVariable(attr)
|
|
|
|
return &v, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Maps make this
|
|
|
|
key = fmt.Sprintf("%s", strings.Join(parts[:i], "."))
|
|
|
|
if attr, ok := r.Primary.Attributes[key]; ok {
|
2016-11-10 22:23:28 -06:00
|
|
|
v, err := hil.InterfaceToVariable(attr)
|
|
|
|
return &v, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MISSING:
|
2015-05-11 16:57:20 -05:00
|
|
|
// Validation for missing interpolations should happen at a higher
|
|
|
|
// semantic level. If we reached this point and don't have variables,
|
|
|
|
// just return the computed value.
|
2015-06-25 00:25:48 -05:00
|
|
|
if scope == nil && scope.Resource == nil {
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return &unknownVariable, nil
|
2015-05-11 16:57:20 -05:00
|
|
|
}
|
|
|
|
|
2015-04-10 15:51:22 -05:00
|
|
|
// If the operation is refresh, it isn't an error for a value to
|
|
|
|
// be unknown. Instead, we return that the value is computed so
|
|
|
|
// that the graph can continue to refresh other nodes. It doesn't
|
|
|
|
// matter because the config isn't interpolated anyways.
|
2015-08-11 11:36:56 -05:00
|
|
|
//
|
|
|
|
// For a Destroy, we're also fine with computed values, since our goal is
|
|
|
|
// only to get destroy nodes for existing resources.
|
|
|
|
//
|
|
|
|
// For an input walk, computed values are okay to return because we're only
|
|
|
|
// looking for missing variables to prompt the user for.
|
2015-10-02 13:20:09 -05:00
|
|
|
if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return &unknownVariable, nil
|
2015-04-10 15:51:22 -05:00
|
|
|
}
|
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return nil, fmt.Errorf(
|
2015-02-05 18:47:06 -06:00
|
|
|
"Resource '%s' does not have attribute '%s' "+
|
|
|
|
"for variable '%s'",
|
|
|
|
id,
|
|
|
|
v.Field,
|
|
|
|
v.FullKey())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interpolater) computeResourceMultiVariable(
|
|
|
|
scope *InterpolationScope,
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
v *config.ResourceVariable) (*ast.Variable, error) {
|
2015-02-05 18:47:06 -06:00
|
|
|
i.StateLock.RLock()
|
|
|
|
defer i.StateLock.RUnlock()
|
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
unknownVariable := unknownVariable()
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
// Get the information about this resource variable, and verify
|
|
|
|
// that it exists and such.
|
|
|
|
module, cr, err := i.resourceVariableInfo(scope, v)
|
|
|
|
if err != nil {
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return nil, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-10-13 19:57:11 -05:00
|
|
|
// Get the keys for all the resources that are created for this resource
|
2016-11-04 13:07:01 -05:00
|
|
|
countMax, err := i.resourceCountMax(module, cr, v)
|
2015-02-05 18:47:06 -06:00
|
|
|
if err != nil {
|
2016-10-13 19:57:11 -05:00
|
|
|
return nil, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-06-23 15:14:09 -05:00
|
|
|
// If count is zero, we return an empty list
|
2016-11-04 13:07:01 -05:00
|
|
|
if countMax == 0 {
|
2016-06-12 05:33:05 -05:00
|
|
|
return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-06-23 15:14:09 -05:00
|
|
|
// If we have no module in the state yet or count, return unknown
|
|
|
|
if module == nil || len(module.Resources) == 0 {
|
|
|
|
return &unknownVariable, nil
|
|
|
|
}
|
2016-07-11 12:40:21 -05:00
|
|
|
|
|
|
|
var values []interface{}
|
2016-11-04 13:07:01 -05:00
|
|
|
for idx := 0; idx < countMax; idx++ {
|
|
|
|
id := fmt.Sprintf("%s.%d", v.ResourceId(), idx)
|
|
|
|
|
2016-10-13 19:57:11 -05:00
|
|
|
// ID doesn't have a trailing index. We try both here, but if a value
|
|
|
|
// without a trailing index is found we prefer that. This choice
|
|
|
|
// is for legacy reasons: older versions of TF preferred it.
|
|
|
|
if id == v.ResourceId()+".0" {
|
|
|
|
potential := v.ResourceId()
|
|
|
|
if _, ok := module.Resources[potential]; ok {
|
|
|
|
id = potential
|
|
|
|
}
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
r, ok := module.Resources[id]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Primary == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
if singleAttr, ok := r.Primary.Attributes[v.Field]; ok {
|
|
|
|
if singleAttr == config.UnknownVariableValue {
|
|
|
|
return &unknownVariable, nil
|
2015-07-05 09:26:01 -05:00
|
|
|
}
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
|
|
|
|
values = append(values, singleAttr)
|
|
|
|
continue
|
2015-07-05 09:26:01 -05:00
|
|
|
}
|
|
|
|
|
2016-07-11 12:40:21 -05:00
|
|
|
// computed list or map attribute
|
|
|
|
_, isList := r.Primary.Attributes[v.Field+".#"]
|
|
|
|
_, isMap := r.Primary.Attributes[v.Field+".%"]
|
|
|
|
if !(isList || isMap) {
|
2015-02-05 18:47:06 -06:00
|
|
|
continue
|
|
|
|
}
|
2016-04-27 16:41:17 -05:00
|
|
|
multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-02-05 18:47:06 -06:00
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
if multiAttr == unknownVariable {
|
|
|
|
return &ast.Variable{Type: ast.TypeString, Value: ""}, nil
|
2015-07-19 19:27:38 -05:00
|
|
|
}
|
|
|
|
|
2016-07-11 12:40:21 -05:00
|
|
|
values = append(values, multiAttr)
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(values) == 0 {
|
2015-06-24 23:23:37 -05:00
|
|
|
// If the operation is refresh, it isn't an error for a value to
|
|
|
|
// be unknown. Instead, we return that the value is computed so
|
|
|
|
// that the graph can continue to refresh other nodes. It doesn't
|
|
|
|
// matter because the config isn't interpolated anyways.
|
2015-08-11 11:36:56 -05:00
|
|
|
//
|
|
|
|
// For a Destroy, we're also fine with computed values, since our goal is
|
|
|
|
// only to get destroy nodes for existing resources.
|
|
|
|
//
|
|
|
|
// For an input walk, computed values are okay to return because we're only
|
|
|
|
// looking for missing variables to prompt the user for.
|
2015-10-02 13:20:09 -05:00
|
|
|
if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return &unknownVariable, nil
|
2015-06-24 23:23:37 -05:00
|
|
|
}
|
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
return nil, fmt.Errorf(
|
2015-02-05 18:47:06 -06:00
|
|
|
"Resource '%s' does not have attribute '%s' "+
|
|
|
|
"for variable '%s'",
|
|
|
|
v.ResourceId(),
|
|
|
|
v.Field,
|
|
|
|
v.FullKey())
|
|
|
|
}
|
|
|
|
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
variable, err := hil.InterfaceToVariable(values)
|
|
|
|
return &variable, err
|
2015-02-05 18:47:06 -06:00
|
|
|
}
|
|
|
|
|
2016-04-27 16:41:17 -05:00
|
|
|
func (i *Interpolater) interpolateComplexTypeAttribute(
|
2015-07-05 09:26:01 -05:00
|
|
|
resourceID string,
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-21 19:03:24 -05:00
|
|
|
attributes map[string]string) (ast.Variable, error) {
|
2015-07-05 09:26:01 -05:00
|
|
|
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
// We can now distinguish between lists and maps in state by the count field:
|
|
|
|
// - lists (and by extension, sets) use the traditional .# notation
|
|
|
|
// - maps use the newer .% notation
|
|
|
|
// Consequently here we can decide how to deal with the keys appropriately
|
|
|
|
// based on whether the type is a map of list.
|
|
|
|
if lengthAttr, isList := attributes[resourceID+".#"]; isList {
|
|
|
|
log.Printf("[DEBUG] Interpolating computed list element attribute %s (%s)",
|
|
|
|
resourceID, lengthAttr)
|
|
|
|
|
|
|
|
// In Terraform's internal dotted representation of list-like attributes, the
|
|
|
|
// ".#" count field is marked as unknown to indicate "this whole list is
|
|
|
|
// unknown". We must honor that meaning here so computed references can be
|
|
|
|
// treated properly during the plan phase.
|
|
|
|
if lengthAttr == config.UnknownVariableValue {
|
|
|
|
return unknownVariable(), nil
|
2016-04-27 16:41:17 -05:00
|
|
|
}
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
|
2016-06-11 06:27:39 -05:00
|
|
|
keys := make([]string, 0)
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
listElementKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
|
2016-07-11 12:40:21 -05:00
|
|
|
for id := range attributes {
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
if listElementKey.MatchString(id) {
|
|
|
|
keys = append(keys, id)
|
|
|
|
}
|
2015-07-05 09:26:01 -05:00
|
|
|
}
|
2016-06-11 06:27:39 -05:00
|
|
|
sort.Strings(keys)
|
2015-07-05 09:26:01 -05:00
|
|
|
|
2016-04-27 16:41:17 -05:00
|
|
|
var members []string
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
for _, key := range keys {
|
2016-04-27 16:41:17 -05:00
|
|
|
members = append(members, attributes[key])
|
|
|
|
}
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
|
2016-04-27 16:41:17 -05:00
|
|
|
return hil.InterfaceToVariable(members)
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if lengthAttr, isMap := attributes[resourceID+".%"]; isMap {
|
|
|
|
log.Printf("[DEBUG] Interpolating computed map element attribute %s (%s)",
|
|
|
|
resourceID, lengthAttr)
|
|
|
|
|
|
|
|
// In Terraform's internal dotted representation of map attributes, the
|
|
|
|
// ".%" count field is marked as unknown to indicate "this whole list is
|
|
|
|
// unknown". We must honor that meaning here so computed references can be
|
|
|
|
// treated properly during the plan phase.
|
|
|
|
if lengthAttr == config.UnknownVariableValue {
|
|
|
|
return unknownVariable(), nil
|
|
|
|
}
|
|
|
|
|
2016-06-11 06:27:39 -05:00
|
|
|
resourceFlatMap := make(map[string]string)
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
mapElementKey := regexp.MustCompile("^" + resourceID + "\\.([^%]+)$")
|
2016-06-11 06:27:39 -05:00
|
|
|
for id, val := range attributes {
|
|
|
|
if mapElementKey.MatchString(id) {
|
|
|
|
resourceFlatMap[id] = val
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-11 06:27:39 -05:00
|
|
|
expanded := flatmap.Expand(resourceFlatMap, resourceID)
|
|
|
|
return hil.InterfaceToVariable(expanded)
|
2016-04-27 16:41:17 -05:00
|
|
|
}
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 03:34:43 -05:00
|
|
|
|
|
|
|
return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID)
|
2015-07-05 09:26:01 -05:00
|
|
|
}
|
|
|
|
|
2015-02-05 18:47:06 -06:00
|
|
|
func (i *Interpolater) resourceVariableInfo(
|
|
|
|
scope *InterpolationScope,
|
|
|
|
v *config.ResourceVariable) (*ModuleState, *config.Resource, error) {
|
|
|
|
// Get the module tree that contains our current path. This is
|
|
|
|
// either the current module (path is empty) or a child.
|
|
|
|
modTree := i.Module
|
|
|
|
if len(scope.Path) > 1 {
|
|
|
|
modTree = i.Module.Child(scope.Path[1:])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the resource from the configuration so we can verify
|
|
|
|
// that the resource is in the configuration and so we can access
|
|
|
|
// the configuration if we need to.
|
|
|
|
var cr *config.Resource
|
|
|
|
for _, r := range modTree.Config().Resources {
|
|
|
|
if r.Id() == v.ResourceId() {
|
|
|
|
cr = r
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if cr == nil {
|
|
|
|
return nil, nil, fmt.Errorf(
|
|
|
|
"Resource '%s' not found for variable '%s'",
|
|
|
|
v.ResourceId(),
|
|
|
|
v.FullKey())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the relevant module
|
|
|
|
module := i.State.ModuleByPath(scope.Path)
|
|
|
|
return module, cr, nil
|
|
|
|
}
|
2016-10-13 19:57:11 -05:00
|
|
|
|
2016-11-04 13:07:01 -05:00
|
|
|
func (i *Interpolater) resourceCountMax(
|
2016-10-13 19:57:11 -05:00
|
|
|
ms *ModuleState,
|
|
|
|
cr *config.Resource,
|
2016-11-04 13:07:01 -05:00
|
|
|
v *config.ResourceVariable) (int, error) {
|
2016-10-13 19:57:11 -05:00
|
|
|
id := v.ResourceId()
|
|
|
|
|
|
|
|
// If we're NOT applying, then we assume we can read the count
|
|
|
|
// from the state. Plan and so on may not have any state yet so
|
|
|
|
// we do a full interpolation.
|
|
|
|
if i.Operation != walkApply {
|
|
|
|
count, err := cr.Count()
|
|
|
|
if err != nil {
|
2016-11-04 13:07:01 -05:00
|
|
|
return 0, err
|
2016-10-13 19:57:11 -05:00
|
|
|
}
|
|
|
|
|
2016-11-04 13:07:01 -05:00
|
|
|
return count, nil
|
2016-10-13 19:57:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// We need to determine the list of resource keys to get values from.
|
|
|
|
// This needs to be sorted so the order is deterministic. We used to
|
|
|
|
// use "cr.Count()" but that doesn't work if the count is interpolated
|
|
|
|
// and we can't guarantee that so we instead depend on the state.
|
2016-11-04 13:07:01 -05:00
|
|
|
max := -1
|
2016-10-13 19:57:11 -05:00
|
|
|
for k, _ := range ms.Resources {
|
2016-11-04 13:07:01 -05:00
|
|
|
// Get the index number for this resource
|
|
|
|
index := ""
|
|
|
|
if k == id {
|
|
|
|
// If the key is the id, then its just 0 (no explicit index)
|
|
|
|
index = "0"
|
|
|
|
} else if strings.HasPrefix(k, id+".") {
|
|
|
|
// Grab the index number out of the state
|
|
|
|
index = k[len(id+"."):]
|
|
|
|
if idx := strings.IndexRune(index, '.'); idx >= 0 {
|
|
|
|
index = index[:idx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there was no index then this resource didn't match
|
|
|
|
// the one we're looking for, exit.
|
|
|
|
if index == "" {
|
2016-10-13 19:57:11 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-11-04 13:07:01 -05:00
|
|
|
// Turn the index into an int
|
|
|
|
raw, err := strconv.ParseInt(index, 0, 0)
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf(
|
|
|
|
"%s: error parsing index %q as int: %s",
|
|
|
|
id, index, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep track of this index if its the max
|
|
|
|
if new := int(raw); new > max {
|
|
|
|
max = new
|
|
|
|
}
|
2016-10-13 19:57:11 -05:00
|
|
|
}
|
2016-11-04 13:07:01 -05:00
|
|
|
|
|
|
|
// If we never found any matching resources in the state, we
|
|
|
|
// have zero.
|
|
|
|
if max == -1 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The result value is "max+1" because we're returning the
|
|
|
|
// max COUNT, not the max INDEX, and we zero-index.
|
|
|
|
return max + 1, nil
|
2016-10-13 19:57:11 -05:00
|
|
|
}
|