Skip to content

Commit e557160

Browse files
committed
check for data source changed during plan
Rather than re-read the data source during every plan cycle, apply the config to the prior state, and skip reading if there is no change. Remove the TODOs, as we're going to accept that data-only changes will still not be plan-able for the time being. Fix the null data source test resource, as it had no computed fields at all, even the id.
1 parent 13101ae commit e557160

File tree

7 files changed

+50
-39
lines changed

7 files changed

+50
-39
lines changed

terraform/context_apply_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8782,6 +8782,16 @@ func TestContext2Apply_dataDependsOn(t *testing.T) {
87828782
if actual != expected {
87838783
t.Fatalf("bad:\n%s", strings.TrimSpace(state.String()))
87848784
}
8785+
8786+
// run another plan to make sure the data source doesn't show as a change
8787+
plan, diags := ctx.Plan()
8788+
assertNoErrors(t, diags)
8789+
8790+
for _, c := range plan.Changes.Resources {
8791+
if c.Action != plans.NoOp {
8792+
t.Fatalf("unexpected change for %s", c.Addr)
8793+
}
8794+
}
87858795
}
87868796

87878797
func TestContext2Apply_terraformWorkspace(t *testing.T) {

terraform/context_plan_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,8 +1892,9 @@ func TestContext2Plan_computedInFunction(t *testing.T) {
18921892
_, diags = ctx.Plan() // should do nothing with data resource in this step, since it was already read
18931893
assertNoErrors(t, diags)
18941894

1895-
if !p.ReadDataSourceCalled {
1896-
t.Fatalf("ReadDataSource should have been called")
1895+
if p.ReadDataSourceCalled {
1896+
// there was no config change to read during plan
1897+
t.Fatalf("ReadDataSource should not have been called")
18971898
}
18981899
}
18991900

terraform/context_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,11 +693,12 @@ func testProviderSchema(name string) *ProviderSchema {
693693
Attributes: map[string]*configschema.Attribute{
694694
"id": {
695695
Type: cty.String,
696-
Optional: true,
696+
Computed: true,
697697
},
698698
"foo": {
699699
Type: cty.String,
700700
Optional: true,
701+
Computed: true,
701702
},
702703
},
703704
},

terraform/eval_read_data.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -236,19 +236,22 @@ func (n *EvalReadDataRefresh) Eval(ctx EvalContext) (interface{}, error) {
236236
}
237237

238238
configKnown := configVal.IsWhollyKnown()
239-
// If our configuration contains any unknown values, or we depend on any
240-
// unknown values then we must defer the read to the apply phase by
241-
// producing a "Read" change for this resource, and a placeholder value for
242-
// it in the state.
243-
if len(n.Config.DependsOn) > 0 || !configKnown {
239+
// If our configuration contains any unknown values, then we must defer the
240+
// read until plan or apply. If we've never read this data source and we
241+
// have any depends_on, we will have to defer reading until plan to resolve
242+
// the dependency changes.
243+
// Assuming we can read the data source with depends_on if we have
244+
// existing state is a compromise to prevent data sources from continually
245+
// showing a diff. We have to make the assumption that if we have a prior
246+
// state, since there are no prior dependency changes happening during
247+
// refresh, that we can read this resource.
248+
if !configKnown || (priorVal.IsNull() && len(n.Config.DependsOn) > 0) {
244249
if configKnown {
245250
log.Printf("[TRACE] EvalReadDataRefresh: %s configuration is fully known, but we're forcing a read plan to be created", absAddr)
246251
} else {
247252
log.Printf("[TRACE] EvalReadDataRefresh: %s configuration not fully known yet, so deferring to apply phase", absAddr)
248253
}
249254

250-
proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal)
251-
252255
// We need to store a change so tat other references to this data
253256
// source can resolve correctly, since the state is not going to be up
254257
// to date.
@@ -258,21 +261,17 @@ func (n *EvalReadDataRefresh) Eval(ctx EvalContext) (interface{}, error) {
258261
Change: plans.Change{
259262
Action: plans.Read,
260263
Before: priorVal,
261-
After: proposedNewVal,
264+
After: objchange.PlannedDataResourceObject(schema, configVal),
262265
},
263266
}
264267

265268
if n.OutputChange != nil {
266269
*n.OutputChange = change
267270
}
271+
268272
if n.State != nil {
269273
*n.State = &states.ResourceInstanceObject{
270-
// We need to keep the prior value in the state so that plan
271-
// has something to diff against.
272-
Value: priorVal,
273-
// TODO: this needs to be ObjectPlanned to trigger a plan, but
274-
// the prior value is lost preventing plan from resulting in a
275-
// NoOp
274+
Value: cty.NullVal(objTy),
276275
Status: states.ObjectPlanned,
277276
}
278277
}
@@ -286,9 +285,8 @@ func (n *EvalReadDataRefresh) Eval(ctx EvalContext) (interface{}, error) {
286285
return nil, diags.ErrWithWarnings()
287286
}
288287

289-
// TODO: Need to signal to plan that this may have changed. We may be able
290-
// to use ObjectPlanned for that, but that currently causes the state to be
291-
// dropped altogether
288+
// This may still have been refreshed with references to resources that
289+
// will be updated, but that will be caught as a change during plan.
292290
outputState := &states.ResourceInstanceObject{
293291
Value: newVal,
294292
Status: states.ObjectReady,

terraform/eval_read_data_plan.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ func (n *EvalReadDataPlan) Eval(ctx EvalContext) (interface{}, error) {
6161
}
6262

6363
configKnown := configVal.IsWhollyKnown()
64-
proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal)
6564
// If our configuration contains any unknown values, or we depend on any
6665
// unknown values then we must defer the read to the apply phase by
6766
// producing a "Read" change for this resource, and a placeholder value for
@@ -73,6 +72,8 @@ func (n *EvalReadDataPlan) Eval(ctx EvalContext) (interface{}, error) {
7372
log.Printf("[TRACE] EvalReadDataPlan: %s configuration not fully known yet, so deferring to apply phase", absAddr)
7473
}
7574

75+
proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal)
76+
7677
err := ctx.Hook(func(h Hook) (HookAction, error) {
7778
return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal)
7879
})
@@ -101,42 +102,43 @@ func (n *EvalReadDataPlan) Eval(ctx EvalContext) (interface{}, error) {
101102
return nil, diags.ErrWithWarnings()
102103
}
103104

105+
// If we have a stored state we may not need to re-read the data source.
106+
// Check the config against the state to see if there are any difference.
107+
if !priorVal.IsNull() {
108+
// Applying the configuration to the prior state lets us see if there
109+
// are any differences.
110+
proposed := objchange.ProposedNewObject(schema, priorVal, configVal)
111+
if proposed.Equals(priorVal).True() {
112+
log.Printf("[TRACE] EvalReadDataPlan: %s no change detected, using existing state", absAddr)
113+
// state looks up to date, and must have been read during refresh
114+
return nil, diags.ErrWithWarnings()
115+
}
116+
}
117+
104118
newVal, readDiags := n.readDataSource(ctx, configVal)
105119
diags = diags.Append(readDiags)
106120
if diags.HasErrors() {
107121
return nil, diags.ErrWithWarnings()
108122
}
109123

110-
action := plans.NoOp
111-
if !newVal.IsNull() && newVal.IsKnown() && newVal.Equals(priorVal).False() {
112-
// since a data source is read-only, update here only means that we
113-
// need to update the state.
114-
action = plans.Update
115-
}
116-
117124
// Produce a change regardless of the outcome.
118125
change := &plans.ResourceInstanceChange{
119126
Addr: absAddr,
120127
ProviderAddr: n.ProviderAddr,
121128
Change: plans.Change{
122-
Action: action,
129+
Action: plans.Update,
123130
Before: priorVal,
124131
After: newVal,
125132
},
126133
}
127134

128-
status := states.ObjectReady
129-
if action == plans.Update {
130-
status = states.ObjectPlanned
131-
}
132-
133135
outputState := &states.ResourceInstanceObject{
134136
Value: newVal,
135-
Status: status,
137+
Status: states.ObjectPlanned,
136138
}
137139

138140
if err := ctx.Hook(func(h Hook) (HookAction, error) {
139-
return h.PostDiff(absAddr, states.CurrentGen, action, priorVal, newVal)
141+
return h.PostDiff(absAddr, states.CurrentGen, plans.Update, priorVal, newVal)
140142
}); err != nil {
141143
return nil, err
142144
}

terraform/testdata/apply-data-depends-on/main.tf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ resource "null_instance" "write" {
33
}
44

55
data "null_data_source" "read" {
6-
foo = ""
76
depends_on = ["null_instance.write"]
87
}

terraform/transform_reference.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type GraphNodeAttachDependencies interface {
5151
// not yet expended in the graph. While this will cause some extra data
5252
// resources to show in the plan when their depends_on references may be in
5353
// unrelated module instances, the fact that it only happens when there are any
54-
// resource updates pending means we ca still avoid the problem of the
54+
// resource updates pending means we can still avoid the problem of the
5555
// "perpetual diff"
5656
type GraphNodeAttachDependsOn interface {
5757
GraphNodeConfigResource
@@ -83,7 +83,7 @@ type GraphNodeReferenceOutside interface {
8383
ReferenceOutside() (selfPath, referencePath addrs.Module)
8484
}
8585

86-
// Referenceeransformer is a GraphTransformer that connects all the
86+
// ReferenceTransformer is a GraphTransformer that connects all the
8787
// nodes that reference each other in order to form the proper ordering.
8888
type ReferenceTransformer struct{}
8989

0 commit comments

Comments
 (0)