core: Tolerate missing resource variables during input walk

Provider nodes interpolate their config during the input walk, but this
is very early and so it's pretty likely that any resources referenced are
entirely absent from the state.

As a special case then, we tolerate the normally-fatal case of having
an entirely missing resource variable so that the input walk can complete,
albeit skipping the providers that have such interpolations.

If these interpolations end up still being unresolved during refresh
(e.g. because the config references a resource that hasn't been created
yet) then we will catch that error on the refresh pass, or indeed on the
plan pass if -refresh=false is used.
This commit is contained in:
Martin Atkins 2016-05-14 09:25:03 -07:00
parent f95dccf1b3
commit 453fc505f4
2 changed files with 99 additions and 16 deletions

View File

@ -230,26 +230,38 @@ func (i *Interpolater) valueResourceVar(
return nil
}
var variable *ast.Variable
var err error
if v.Multi && v.Index == -1 {
variable, err := i.computeResourceMultiVariable(scope, v)
if err != nil {
return err
}
if variable == nil {
return fmt.Errorf("no error reported by variable %q is nil", v.Name)
}
result[n] = *variable
variable, err = i.computeResourceMultiVariable(scope, v)
} else {
variable, err := i.computeResourceVariable(scope, v)
if err != nil {
return err
}
if variable == nil {
return fmt.Errorf("no error reported by variable %q is nil", v.Name)
}
result[n] = *variable
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 {
result[n] = ast.Variable{
Value: config.UnknownVariableValue,
Type: ast.TypeString,
}
return nil
}
return fmt.Errorf("variable %q is nil, but no error was reported", v.Name)
}
result[n] = *variable
return nil
}

View File

@ -174,6 +174,60 @@ func TestInterpolater_resourceVariable(t *testing.T) {
})
}
func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
// During the input walk, computed resource attributes may be entirely
// absent since we've not yet produced diffs that tell us what computed
// attributes to expect. In that case, interpolator tolerates it and
// indicates the value is computed.
lock := new(sync.RWMutex)
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
// No resources at all yet, because we're still dealing
// with input and so the resources haven't been created.
},
},
},
}
{
i := &Interpolater{
Operation: walkInput,
Module: testModule(t, "interpolate-resource-variable"),
State: state,
StateLock: lock,
}
scope := &InterpolationScope{
Path: rootModulePath,
}
testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
Value: config.UnknownVariableValue,
Type: ast.TypeString,
})
}
// This doesn't apply during other walks, like plan
{
i := &Interpolater{
Operation: walkPlan,
Module: testModule(t, "interpolate-resource-variable"),
State: state,
StateLock: lock,
}
scope := &InterpolationScope{
Path: rootModulePath,
}
testInterpolateErr(t, i, scope, "aws_instance.web.foo")
}
}
func TestInterpolater_resourceVariableMulti(t *testing.T) {
lock := new(sync.RWMutex)
state := &State{
@ -473,3 +527,20 @@ func testInterpolate(
t.Fatalf("%q: actual: %#v\nexpected: %#v", n, actual, expected)
}
}
func testInterpolateErr(
t *testing.T, i *Interpolater,
scope *InterpolationScope,
n string) {
v, err := config.NewInterpolatedVariable(n)
if err != nil {
t.Fatalf("err: %s", err)
}
_, err = i.Values(scope, map[string]config.InterpolatedVariable{
"foo": v,
})
if err == nil {
t.Fatalf("%q: succeeded, but wanted error", n)
}
}