Skip to content

Commit

Permalink
Tolerate missing resource variables during input walk
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apparentlymart committed May 14, 2016
1 parent f95dccf commit b093de4
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 15 deletions.
42 changes: 27 additions & 15 deletions terraform/interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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
}
result[n] = *variable

return fmt.Errorf("variable %q is nil, but no error was reported", v.Name)
}

result[n] = *variable
return nil
}

Expand Down
71 changes: 71 additions & 0 deletions terraform/interpolate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
}
}

0 comments on commit b093de4

Please sign in to comment.