terraform: multi-var interpolation should use state for count
Related to #5254 If the count of a resource is interpolated (i.e. `${var.c}`), then it must be interpolated before any splat variable using that resource can be used (i.e. `type.name.*.attr`). The original fix for #5254 is to always ensure that this is the case. While working on a new apply builder based on the diff in `f-apply-builder`, this truth no longer always holds. Rather than always include such a resource, I believe the correct behavior instead is to use the state as a source of truth during `walkApply` operations. This change specifically is scoped to `walkApply` operation interpolations since we know the state of any multi-variable should be available. The behavior is less clear for other operations so I left the logic unchanged from prior versions.
This commit is contained in:
parent
fecc218505
commit
728a1e5448
|
@ -490,17 +490,14 @@ func (i *Interpolater) computeResourceMultiVariable(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the count so we know how many to iterate over
|
// Get the keys for all the resources that are created for this resource
|
||||||
count, err := cr.Count()
|
resourceKeys, err := i.resourceCountKeys(module, cr, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, err
|
||||||
"Error reading %s count: %s",
|
|
||||||
v.ResourceId(),
|
|
||||||
err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If count is zero, we return an empty list
|
// If count is zero, we return an empty list
|
||||||
if count == 0 {
|
if len(resourceKeys) == 0 {
|
||||||
return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
|
return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,13 +507,15 @@ func (i *Interpolater) computeResourceMultiVariable(
|
||||||
}
|
}
|
||||||
|
|
||||||
var values []interface{}
|
var values []interface{}
|
||||||
for j := 0; j < count; j++ {
|
for _, id := range resourceKeys {
|
||||||
id := fmt.Sprintf("%s.%d", v.ResourceId(), j)
|
// 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
|
||||||
// If we're dealing with only a single resource, then the
|
// is for legacy reasons: older versions of TF preferred it.
|
||||||
// ID doesn't have a trailing index.
|
if id == v.ResourceId()+".0" {
|
||||||
if count == 1 {
|
potential := v.ResourceId()
|
||||||
id = v.ResourceId()
|
if _, ok := module.Resources[potential]; ok {
|
||||||
|
id = potential
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := module.Resources[id]
|
r, ok := module.Resources[id]
|
||||||
|
@ -678,3 +677,44 @@ func (i *Interpolater) resourceVariableInfo(
|
||||||
module := i.State.ModuleByPath(scope.Path)
|
module := i.State.ModuleByPath(scope.Path)
|
||||||
return module, cr, nil
|
return module, cr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Interpolater) resourceCountKeys(
|
||||||
|
ms *ModuleState,
|
||||||
|
cr *config.Resource,
|
||||||
|
v *config.ResourceVariable) ([]string, 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 {
|
||||||
|
count, err := cr.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
result[i] = fmt.Sprintf("%s.%d", id, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, 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.
|
||||||
|
var resourceKeys []string
|
||||||
|
for k, _ := range ms.Resources {
|
||||||
|
// If we don't have the right prefix then ignore it
|
||||||
|
if k != id && !strings.HasPrefix(k, id+".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to the list
|
||||||
|
resourceKeys = append(resourceKeys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(resourceKeys)
|
||||||
|
return resourceKeys, nil
|
||||||
|
}
|
||||||
|
|
|
@ -358,6 +358,49 @@ func TestInterpolater_resourceVariableMulti(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) {
|
||||||
|
lock := new(sync.RWMutex)
|
||||||
|
state := &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web.0": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "a",
|
||||||
|
Attributes: map[string]string{"foo": "a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"aws_instance.web.1": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "b",
|
||||||
|
Attributes: map[string]string{"foo": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &Interpolater{
|
||||||
|
Operation: walkApply,
|
||||||
|
Module: testModule(t, "interpolate-multi-interp"),
|
||||||
|
State: state,
|
||||||
|
StateLock: lock,
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := &InterpolationScope{
|
||||||
|
Path: rootModulePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []interface{}{"a", "b"}
|
||||||
|
testInterpolate(t, i, scope, "aws_instance.web.*.foo",
|
||||||
|
interfaceToVariableSwallowError(expected))
|
||||||
|
}
|
||||||
|
|
||||||
func interfaceToVariableSwallowError(input interface{}) ast.Variable {
|
func interfaceToVariableSwallowError(input interface{}) ast.Variable {
|
||||||
variable, _ := hil.InterfaceToVariable(input)
|
variable, _ := hil.InterfaceToVariable(input)
|
||||||
return variable
|
return variable
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "${var.c}"
|
||||||
|
}
|
Loading…
Reference in New Issue