From b093de49f39cec0a34c7b2e48d8552ccec357410 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Sat, 14 May 2016 09:22:53 -0700 Subject: [PATCH] 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. --- terraform/interpolate.go | 42 +++++++++++++-------- terraform/interpolate_test.go | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 11eb193b61a2..cb19d4eb6ede 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -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 } diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index e3777ae4a3d6..9485512a2978 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -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) + } +}