func (c *Context) computeResourceVariable( v *config.ResourceVariable) (string, error) { id := v.ResourceId() if v.Multi { id = fmt.Sprintf("%s.%d", id, v.Index) } c.sl.RLock() defer c.sl.RUnlock() r, ok := c.state.Resources[id] if !ok { return "", fmt.Errorf( "Resource '%s' not found for variable '%s'", id, v.FullKey()) } attr, ok := r.Attributes[v.Field] if !ok { return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", id, v.Field, v.FullKey()) } return attr, nil }
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 }
func (c *walkContext) resourceVariableInfo( 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. var modTree *module.Tree childPath := c.Path[1:len(c.Path)] if len(childPath) == 0 { modTree = c.Context.module } else { modTree = c.Context.module.Child(childPath) } // 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 := c.Context.state.ModuleByPath(c.Path) return module, cr, nil }
func (c *Context) computeResourceMultiVariable( v *config.ResourceVariable) (string, error) { c.sl.RLock() defer c.sl.RUnlock() // Get the resource from the configuration so we can know how // many of the resource there is. var cr *config.Resource for _, r := range c.config.Resources { if r.Id() == v.ResourceId() { cr = r break } } if cr == nil { return "", fmt.Errorf( "Resource '%s' not found for variable '%s'", v.ResourceId(), v.FullKey()) } var values []string for i := 0; i < cr.Count; i++ { id := fmt.Sprintf("%s.%d", v.ResourceId(), i) // If we're dealing with only a single resource, then the // ID doesn't have a trailing index. if cr.Count == 1 { id = v.ResourceId() } r, ok := c.state.Resources[id] if !ok { continue } attr, ok := r.Attributes[v.Field] if !ok { continue } values = append(values, attr) } if len(values) == 0 { return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", v.ResourceId(), v.Field, v.FullKey()) } return strings.Join(values, ","), nil }
func (c *Context) computeResourceVariable( v *config.ResourceVariable) (string, error) { id := v.ResourceId() if v.Multi { id = fmt.Sprintf("%s.%d", id, v.Index) } c.sl.RLock() defer c.sl.RUnlock() r, ok := c.state.Resources[id] if !ok { return "", fmt.Errorf( "Resource '%s' not found for variable '%s'", id, v.FullKey()) } attr, ok := r.Attributes[v.Field] if ok { return attr, nil } // 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. parts := strings.Split(v.Field, ".") if len(parts) > 1 { for i := 1; i < len(parts); i++ { key := fmt.Sprintf("%s.#", strings.Join(parts[:i], ".")) if attr, ok := r.Attributes[key]; ok { return attr, nil } } } return "", 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) (string, error) { i.StateLock.RLock() defer i.StateLock.RUnlock() // 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 "", err } // Get the count so we know how many to iterate over count, err := cr.Count() if err != nil { return "", fmt.Errorf( "Error reading %s count: %s", v.ResourceId(), err) } // If we have no module in the state yet or count, return empty if module == nil || len(module.Resources) == 0 || count == 0 { return "", nil } var values []string for i := 0; i < count; i++ { id := fmt.Sprintf("%s.%d", v.ResourceId(), i) // If we're dealing with only a single resource, then the // ID doesn't have a trailing index. if count == 1 { id = v.ResourceId() } r, ok := module.Resources[id] if !ok { continue } if r.Primary == nil { continue } attr, ok := r.Primary.Attributes[v.Field] if !ok { continue } values = append(values, attr) } 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. if i.Operation == walkRefresh { return config.UnknownVariableValue, nil } return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", v.ResourceId(), v.Field, v.FullKey()) } return strings.Join(values, config.InterpSplitDelim), nil }
func (i *Interpolater) computeResourceVariable( scope *InterpolationScope, v *config.ResourceVariable) (string, error) { id := v.ResourceId() if v.Multi { id = fmt.Sprintf("%s.%d", id, v.Index) } i.StateLock.RLock() defer i.StateLock.RUnlock() // Get the information about this resource variable, and verify // that it exists and such. module, _, err := i.resourceVariableInfo(scope, v) if err != nil { return "", err } // If we have no module in the state yet or count, return empty if module == nil || len(module.Resources) == 0 { return "", 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. 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 { goto MISSING } if r.Primary == nil { goto MISSING } if attr, ok := r.Primary.Attributes[v.Field]; ok { return attr, nil } // 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 { 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 { return attr, nil } // Maps make this key = fmt.Sprintf("%s", strings.Join(parts[:i], ".")) if attr, ok := r.Primary.Attributes[key]; ok { return attr, nil } } } 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 config.UnknownVariableValue, 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. if i.Operation == walkRefresh { return config.UnknownVariableValue, nil } return "", 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) (string, error) { i.StateLock.RLock() defer i.StateLock.RUnlock() // 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 "", err } // Get the count so we know how many to iterate over count, err := cr.Count() if err != nil { return "", fmt.Errorf( "Error reading %s count: %s", v.ResourceId(), err) } // If we have no module in the state yet or count, return empty if module == nil || len(module.Resources) == 0 || count == 0 { return "", nil } var values []string for j := 0; j < count; j++ { id := fmt.Sprintf("%s.%d", v.ResourceId(), j) // If we're dealing with only a single resource, then the // ID doesn't have a trailing index. if count == 1 { id = v.ResourceId() } r, ok := module.Resources[id] if !ok { continue } if r.Primary == nil { continue } attr, ok := r.Primary.Attributes[v.Field] if !ok { // computed list attribute _, ok := r.Primary.Attributes[v.Field+".#"] if !ok { continue } attr, err = i.interpolateListAttribute(v.Field, r.Primary.Attributes) if err != nil { return "", err } } if config.IsStringList(attr) { for _, s := range config.StringList(attr).Slice() { values = append(values, s) } continue } // If any value is unknown, the whole thing is unknown if attr == config.UnknownVariableValue { return config.UnknownVariableValue, nil } values = append(values, attr) } 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 config.UnknownVariableValue, nil } return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", v.ResourceId(), v.Field, v.FullKey()) } return config.NewStringList(values).String(), nil }
func (i *Interpolater) computeResourceMultiVariable( scope *InterpolationScope, v *config.ResourceVariable) (*ast.Variable, error) { i.StateLock.RLock() defer i.StateLock.RUnlock() unknownVariable := unknownVariable() // 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) 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 == walkDestroy || 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() // 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 count so we know how many to iterate over count, err := cr.Count() if err != nil { return nil, fmt.Errorf( "Error reading %s count: %s", v.ResourceId(), err) } // If count is zero, we return an empty list if count == 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 j := 0; j < count; j++ { id := fmt.Sprintf("%s.%d", v.ResourceId(), j) // If we're dealing with only a single resource, then the // ID doesn't have a trailing index. if count == 1 { id = v.ResourceId() } 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 &ast.Variable{Type: ast.TypeString, Value: ""}, 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 (c *walkContext) computeResourceMultiVariable( v *config.ResourceVariable) (string, error) { c.Context.sl.RLock() defer c.Context.sl.RUnlock() // Get the resource from the configuration so we can know how // many of the resource there is. var cr *config.Resource for _, r := range c.Context.module.Config().Resources { if r.Id() == v.ResourceId() { cr = r break } } if cr == nil { return "", fmt.Errorf( "Resource '%s' not found for variable '%s'", v.ResourceId(), v.FullKey()) } // Get the relevant module // TODO: Not use only root module module := c.Context.state.RootModule() count, err := cr.Count() if err != nil { return "", fmt.Errorf( "Error reading %s count: %s", v.ResourceId(), err) } // If we have no count, return empty if count == 0 { return "", nil } var values []string for i := 0; i < count; i++ { id := fmt.Sprintf("%s.%d", v.ResourceId(), i) // If we're dealing with only a single resource, then the // ID doesn't have a trailing index. if count == 1 { id = v.ResourceId() } r, ok := module.Resources[id] if !ok { continue } if r.Primary == nil { continue } attr, ok := r.Primary.Attributes[v.Field] if !ok { continue } values = append(values, attr) } if len(values) == 0 { return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", v.ResourceId(), v.Field, v.FullKey()) } return strings.Join(values, config.InterpSplitDelim), nil }
func (c *walkContext) computeResourceVariable( v *config.ResourceVariable) (string, error) { id := v.ResourceId() if v.Multi { id = fmt.Sprintf("%s.%d", id, v.Index) } c.Context.sl.RLock() defer c.Context.sl.RUnlock() // Get the relevant module module := c.Context.state.ModuleByPath(c.Path) var r *ResourceState if module != nil { 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 { return "", fmt.Errorf( "Resource '%s' not found for variable '%s'", id, v.FullKey()) } if r.Primary == nil { goto MISSING } if attr, ok := r.Primary.Attributes[v.Field]; ok { return attr, nil } // 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++ { key := fmt.Sprintf("%s.#", strings.Join(parts[:i], ".")) if attr, ok := r.Primary.Attributes[key]; ok { return attr, nil } } } MISSING: return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", id, v.Field, v.FullKey()) }
func (c *walkContext) computeResourceMultiVariable( v *config.ResourceVariable) (string, error) { c.Context.sl.RLock() defer c.Context.sl.RUnlock() // Get the information about this resource variable, and verify // that it exists and such. module, cr, err := c.resourceVariableInfo(v) if err != nil { return "", err } // Get the count so we know how many to iterate over count, err := cr.Count() if err != nil { return "", fmt.Errorf( "Error reading %s count: %s", v.ResourceId(), err) } // If we have no module in the state yet or count, return empty if module == nil || len(module.Resources) == 0 || count == 0 { return "", nil } var values []string for i := 0; i < count; i++ { id := fmt.Sprintf("%s.%d", v.ResourceId(), i) // If we're dealing with only a single resource, then the // ID doesn't have a trailing index. if count == 1 { id = v.ResourceId() } r, ok := module.Resources[id] if !ok { continue } if r.Primary == nil { continue } attr, ok := r.Primary.Attributes[v.Field] if !ok { continue } values = append(values, attr) } if len(values) == 0 { return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", v.ResourceId(), v.Field, v.FullKey()) } return strings.Join(values, config.InterpSplitDelim), nil }
func (c *walkContext) computeResourceVariable( v *config.ResourceVariable) (string, error) { id := v.ResourceId() if v.Multi { id = fmt.Sprintf("%s.%d", id, v.Index) } c.Context.sl.RLock() defer c.Context.sl.RUnlock() // Get the information about this resource variable, and verify // that it exists and such. module, _, err := c.resourceVariableInfo(v) if err != nil { return "", err } // If we have no module in the state yet or count, return empty if module == nil || len(module.Resources) == 0 { return "", 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. 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 { return "", fmt.Errorf( "Resource '%s' not found for variable '%s'", id, v.FullKey()) } if r.Primary == nil { goto MISSING } if attr, ok := r.Primary.Attributes[v.Field]; ok { return attr, nil } // 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 c.Operation == walkApply { 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 { return attr, nil } // Maps make this key = fmt.Sprintf("%s", strings.Join(parts[:i], ".")) if attr, ok := r.Primary.Attributes[key]; ok { return attr, nil } } } MISSING: return "", fmt.Errorf( "Resource '%s' does not have attribute '%s' "+ "for variable '%s'", id, v.Field, v.FullKey()) }