diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/interpolate.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/terraform/interpolate.go | 790 |
1 files changed, 790 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/interpolate.go b/vendor/github.com/hashicorp/terraform/terraform/interpolate.go new file mode 100644 index 00000000..855548c0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/terraform/interpolate.go @@ -0,0 +1,790 @@ +package terraform + +import ( + "fmt" + "log" + "os" + "strconv" + "strings" + "sync" + + "github.com/hashicorp/hil" + "github.com/hashicorp/hil/ast" + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/flatmap" +) + +const ( + // VarEnvPrefix is the prefix of variables that are read from + // the environment to set variables here. + VarEnvPrefix = "TF_VAR_" +) + +// Interpolater is the structure responsible for determining the values +// for interpolations such as `aws_instance.foo.bar`. +type Interpolater struct { + Operation walkOperation + Meta *ContextMeta + Module *module.Tree + State *State + StateLock *sync.RWMutex + VariableValues map[string]interface{} + VariableValuesLock *sync.Mutex +} + +// 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) { + if scope == nil { + scope = &InterpolationScope{} + } + + result := make(map[string]ast.Variable, len(vars)) + + // 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 { + // Set default variables + if v.Default == nil { + continue + } + + 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) + } + + result[n] = variable + } + } + + 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) + case *config.SelfVariable: + err = i.valueSelfVar(scope, n, v, result) + case *config.SimpleVariable: + err = i.valueSimpleVar(scope, n, v, result) + case *config.TerraformVariable: + err = i.valueTerraformVar(scope, n, v, result) + 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: + if scope.Resource == nil { + return fmt.Errorf("%s: count.index is only valid within resources", n) + } + 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) + } +} + +func unknownVariable() ast.Variable { + return ast.Variable{ + Type: ast.TypeUnknown, + Value: config.UnknownVariableValue, + } +} + +func unknownValue() string { + return hil.UnknownValue +} + +func (i *Interpolater) valueModuleVar( + scope *InterpolationScope, + n string, + v *config.ModuleVariable, + result map[string]ast.Variable) error { + + // 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. + result[n] = unknownVariable() + + // During apply this is always an error + if i.Operation == walkApply { + return fmt.Errorf( + "Couldn't find module %q for var: %s", + v.Name, v.FullKey()) + } + } else { + // Get the value from the outputs + if outputState, ok := mod.Outputs[v.Field]; ok { + output, err := hil.InterfaceToVariable(outputState.Value) + if err != nil { + return err + } + result[n] = output + } else { + // Same reasons as the comment above. + result[n] = unknownVariable() + + // During apply this is always an error + if i.Operation == walkApply { + return fmt.Errorf( + "Couldn't find output %q for module var: %s", + v.Field, v.FullKey()) + } + } + } + + 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. + if i.Operation == walkValidate { + result[n] = unknownVariable() + return nil + } + + var variable *ast.Variable + var err error + + if v.Multi && v.Index == -1 { + variable, err = i.computeResourceMultiVariable(scope, v) + } else { + 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 || i.Operation == walkRefresh { + result[n] = unknownVariable() + return nil + } + + return fmt.Errorf("variable %q is nil, but no error was reported", v.Name) + } + + result[n] = *variable + return nil +} + +func (i *Interpolater) valueSelfVar( + scope *InterpolationScope, + n string, + v *config.SelfVariable, + result map[string]ast.Variable) error { + if scope == nil || scope.Resource == nil { + return fmt.Errorf( + "%s: invalid scope, self variables are only valid on resources", n) + } + + 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) +} + +func (i *Interpolater) valueSimpleVar( + scope *InterpolationScope, + n string, + v *config.SimpleVariable, + result map[string]ast.Variable) error { + // 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. Did you mean 'var.%s'? 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, n) +} + +func (i *Interpolater) valueTerraformVar( + scope *InterpolationScope, + n string, + v *config.TerraformVariable, + result map[string]ast.Variable) error { + if v.Field != "env" { + return fmt.Errorf( + "%s: only supported key for 'terraform.X' interpolations is 'env'", n) + } + + if i.Meta == nil { + return fmt.Errorf( + "%s: internal error: nil Meta. Please report a bug.", n) + } + + result[n] = ast.Variable{Type: ast.TypeString, Value: i.Meta.Env} + return nil +} + +func (i *Interpolater) valueUserVar( + scope *InterpolationScope, + n string, + v *config.UserVariable, + result map[string]ast.Variable) error { + i.VariableValuesLock.Lock() + defer i.VariableValuesLock.Unlock() + val, ok := i.VariableValues[v.Name] + if ok { + 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) + } + result[n] = varValue + return nil + } + + if _, ok := result[n]; !ok && i.Operation == walkValidate { + result[n] = unknownVariable() + return nil + } + + // Look up if we have any variables with this prefix because + // those are map overrides. Include those. + for k, val := range i.VariableValues { + if strings.HasPrefix(k, v.Name+".") { + 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) + } + mapVariable[overrideKey] = varValue + } + } + + return nil +} + +func (i *Interpolater) computeResourceVariable( + scope *InterpolationScope, + v *config.ResourceVariable) (*ast.Variable, error) { + id := v.ResourceId() + if v.Multi { + id = fmt.Sprintf("%s.%d", id, v.Index) + } + + i.StateLock.RLock() + defer i.StateLock.RUnlock() + + unknownVariable := unknownVariable() + + // These variables must be declared early because of the use of GOTO + var isList bool + var isMap bool + + // Get the information about this resource variable, and verify + // that it exists and such. + module, cr, err := i.resourceVariableInfo(scope, v) + if err != nil { + return nil, err + } + + // If we're requesting "count" its a special variable that we grab + // directly from the config itself. + if v.Field == "count" { + var count int + if cr != nil { + count, err = cr.Count() + } else { + count, err = i.resourceCountMax(module, cr, v) + } + if err != nil { + return nil, fmt.Errorf( + "Error reading %s count: %s", + v.ResourceId(), + err) + } + + return &ast.Variable{Type: ast.TypeInt, Value: count}, nil + } + + // 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. + var r *ResourceState + if module != nil && len(module.Resources) > 0 { + var ok bool + 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 || r.Primary == nil { + if i.Operation == walkApply || i.Operation == walkPlan { + return nil, fmt.Errorf( + "Resource '%s' not found for variable '%s'", + v.ResourceId(), + v.FullKey()) + } + + // If we have no module in the state yet or count, return empty. + // NOTE(@mitchellh): I actually don't know why this is here. During + // a refactor I kept this here to maintain the same behavior, but + // I'm not sure why its here. + if module == nil || len(module.Resources) == 0 { + return nil, nil + } + + goto MISSING + } + + if attr, ok := r.Primary.Attributes[v.Field]; ok { + v, err := hil.InterfaceToVariable(attr) + return &v, err + } + + // computed list or map attribute + _, isList = r.Primary.Attributes[v.Field+".#"] + _, isMap = r.Primary.Attributes[v.Field+".%"] + if isList || isMap { + variable, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes) + return &variable, err + } + + // 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. + if i.Operation == walkApply || i.Operation == walkDestroy { + 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 { + v, err := hil.InterfaceToVariable(attr) + return &v, err + } + + // Maps make this + key = fmt.Sprintf("%s", strings.Join(parts[:i], ".")) + if attr, ok := r.Primary.Attributes[key]; ok { + v, err := hil.InterfaceToVariable(attr) + return &v, err + } + } + } + +MISSING: + // 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. + if scope == nil && scope.Resource == nil { + return &unknownVariable, nil + } + + // 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. + // + // 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. + if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkInput { + return &unknownVariable, nil + } + + return nil, fmt.Errorf( + "Resource '%s' does not have attribute '%s' "+ + "for variable '%s'", + id, + v.Field, + v.FullKey()) +} + +func (i *Interpolater) computeResourceMultiVariable( + scope *InterpolationScope, + v *config.ResourceVariable) (*ast.Variable, error) { + i.StateLock.RLock() + defer i.StateLock.RUnlock() + + unknownVariable := unknownVariable() + + // If we're only looking for input, we don't need to expand a + // multi-variable. This prevents us from encountering things that should be + // known but aren't because the state has yet to be refreshed. + if i.Operation == walkInput { + return &unknownVariable, nil + } + + // Get the information about this resource variable, and verify + // that it exists and such. + module, cr, err := i.resourceVariableInfo(scope, v) + if err != nil { + return nil, err + } + + // Get the keys for all the resources that are created for this resource + countMax, err := i.resourceCountMax(module, cr, v) + if err != nil { + return nil, err + } + + // If count is zero, we return an empty list + if countMax == 0 { + return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil + } + + // If we have no module in the state yet or count, return unknown + if module == nil || len(module.Resources) == 0 { + return &unknownVariable, nil + } + + var values []interface{} + for idx := 0; idx < countMax; idx++ { + id := fmt.Sprintf("%s.%d", v.ResourceId(), idx) + + // 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 + } + } + + r, ok := module.Resources[id] + if !ok { + continue + } + + if r.Primary == nil { + continue + } + + if singleAttr, ok := r.Primary.Attributes[v.Field]; ok { + if singleAttr == config.UnknownVariableValue { + return &unknownVariable, nil + } + + values = append(values, singleAttr) + continue + } + + // computed list or map attribute + _, isList := r.Primary.Attributes[v.Field+".#"] + _, isMap := r.Primary.Attributes[v.Field+".%"] + if !(isList || isMap) { + continue + } + multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes) + if err != nil { + return nil, err + } + + if multiAttr == unknownVariable { + return &unknownVariable, nil + } + + values = append(values, multiAttr) + } + + if len(values) == 0 { + // 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. + // + // 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. + if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput { + return &unknownVariable, nil + } + + return nil, fmt.Errorf( + "Resource '%s' does not have attribute '%s' "+ + "for variable '%s'", + v.ResourceId(), + v.Field, + v.FullKey()) + } + + variable, err := hil.InterfaceToVariable(values) + return &variable, err +} + +func (i *Interpolater) interpolateComplexTypeAttribute( + resourceID string, + attributes map[string]string) (ast.Variable, error) { + + // 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 + } + + expanded := flatmap.Expand(attributes, resourceID) + return hil.InterfaceToVariable(expanded) + } + + 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 + } + + expanded := flatmap.Expand(attributes, resourceID) + return hil.InterfaceToVariable(expanded) + } + + return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID) +} + +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 + } + } + + // Get the relevant module + module := i.State.ModuleByPath(scope.Path) + return module, cr, nil +} + +func (i *Interpolater) resourceCountMax( + ms *ModuleState, + cr *config.Resource, + v *config.ResourceVariable) (int, error) { + 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 { + if cr == nil { + return 0, nil + } + + count, err := cr.Count() + if err != nil { + return 0, err + } + + return count, nil + } + + // 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. + max := -1 + for k, _ := range ms.Resources { + // 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 == "" { + continue + } + + // 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 + } + } + + // 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 +} |