Skip to content

Commit e690fa1

Browse files
authored
Merge pull request #24904 from hashicorp/jbardin/plan-data-sources
Evaluate data sources in plan when necessary
2 parents 0d62001 + a8e0914 commit e690fa1

19 files changed

+821
-521
lines changed

plans/changes.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInst
5252
}
5353

5454
return nil
55+
56+
}
57+
58+
// InstancesForConfigResource returns the planned change for the current objects
59+
// of the resource instances of the given address, if any. Returns nil if no
60+
// changes are planned.
61+
func (c *Changes) InstancesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc {
62+
var changes []*ResourceInstanceChangeSrc
63+
for _, rc := range c.Resources {
64+
resAddr := rc.Addr.ContainingResource().Config()
65+
if resAddr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
66+
changes = append(changes, rc)
67+
}
68+
}
69+
70+
return changes
5571
}
5672

5773
// ResourceInstanceDeposed returns the plan change of a deposed object of

plans/changes_sync.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,29 @@ func (cs *ChangesSync) GetResourceInstanceChange(addr addrs.AbsResourceInstance,
6262
panic(fmt.Sprintf("unsupported generation value %#v", gen))
6363
}
6464

65+
// GetChangesForConfigResource searched the set of resource instance
66+
// changes and returns all changes related to a given configuration address.
67+
// This is be used to find possible changes related to a configuration
68+
// reference.
69+
//
70+
// If no such changes exist, nil is returned.
71+
//
72+
// The returned objects are a deep copy of the change recorded in the plan, so
73+
// callers may mutate them although it's generally better (less confusing) to
74+
// treat planned changes as immutable after they've been initially constructed.
75+
func (cs *ChangesSync) GetChangesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc {
76+
if cs == nil {
77+
panic("GetChangesForConfigResource on nil ChangesSync")
78+
}
79+
cs.lock.Lock()
80+
defer cs.lock.Unlock()
81+
var changes []*ResourceInstanceChangeSrc
82+
for _, c := range cs.changes.InstancesForConfigResource(addr) {
83+
changes = append(changes, c.DeepCopy())
84+
}
85+
return changes
86+
}
87+
6588
// RemoveResourceInstanceChange searches the set of resource instance changes
6689
// for one matching the given address and generation, and removes it from the
6790
// set if it exists.

terraform/context_apply_test.go

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8724,7 +8724,20 @@ func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testin
87248724
// that resource to be applied first.
87258725
func TestContext2Apply_dataDependsOn(t *testing.T) {
87268726
p := testProvider("null")
8727-
m := testModule(t, "apply-data-depends-on")
8727+
m := testModuleInline(t, map[string]string{
8728+
"main.tf": `
8729+
resource "null_instance" "write" {
8730+
foo = "attribute"
8731+
}
8732+
8733+
data "null_data_source" "read" {
8734+
depends_on = ["null_instance.write"]
8735+
}
8736+
8737+
resource "null_instance" "depends" {
8738+
foo = data.null_data_source.read.foo
8739+
}
8740+
`})
87288741

87298742
ctx := testContext2(t, &ContextOpts{
87308743
Config: m,
@@ -8782,6 +8795,63 @@ func TestContext2Apply_dataDependsOn(t *testing.T) {
87828795
if actual != expected {
87838796
t.Fatalf("bad:\n%s", strings.TrimSpace(state.String()))
87848797
}
8798+
8799+
// run another plan to make sure the data source doesn't show as a change
8800+
plan, diags := ctx.Plan()
8801+
assertNoErrors(t, diags)
8802+
8803+
for _, c := range plan.Changes.Resources {
8804+
if c.Action != plans.NoOp {
8805+
t.Fatalf("unexpected change for %s", c.Addr)
8806+
}
8807+
}
8808+
8809+
// now we cause a change in the first resource, which should trigger a plan
8810+
// in the data source, and the resource that depends on the data source
8811+
// must plan a change as well.
8812+
m = testModuleInline(t, map[string]string{
8813+
"main.tf": `
8814+
resource "null_instance" "write" {
8815+
foo = "new"
8816+
}
8817+
8818+
data "null_data_source" "read" {
8819+
depends_on = ["null_instance.write"]
8820+
}
8821+
8822+
resource "null_instance" "depends" {
8823+
foo = data.null_data_source.read.foo
8824+
}
8825+
`})
8826+
8827+
p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) {
8828+
// the side effect of the resource being applied
8829+
provisionerOutput = "APPLIED_AGAIN"
8830+
return testApplyFn(info, s, d)
8831+
}
8832+
8833+
ctx = testContext2(t, &ContextOpts{
8834+
Config: m,
8835+
State: state,
8836+
Providers: map[addrs.Provider]providers.Factory{
8837+
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
8838+
},
8839+
})
8840+
8841+
plan, diags = ctx.Plan()
8842+
assertNoErrors(t, diags)
8843+
8844+
expectedChanges := map[string]plans.Action{
8845+
"null_instance.write": plans.Update,
8846+
"data.null_data_source.read": plans.Read,
8847+
"null_instance.depends": plans.Update,
8848+
}
8849+
8850+
for _, c := range plan.Changes.Resources {
8851+
if c.Action != expectedChanges[c.Addr.String()] {
8852+
t.Errorf("unexpected %s for %s", c.Action, c.Addr)
8853+
}
8854+
}
87858855
}
87868856

87878857
func TestContext2Apply_terraformWorkspace(t *testing.T) {

terraform/context_plan_test.go

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,9 +1893,9 @@ func TestContext2Plan_computedInFunction(t *testing.T) {
18931893
assertNoErrors(t, diags)
18941894

18951895
if p.ReadDataSourceCalled {
1896-
t.Fatalf("ReadDataSource was called on provider during plan; should not have been called")
1896+
// there was no config change to read during plan
1897+
t.Fatalf("ReadDataSource should not have been called")
18971898
}
1898-
18991899
}
19001900

19011901
func TestContext2Plan_computedDataCountResource(t *testing.T) {
@@ -1993,6 +1993,7 @@ func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) {
19931993
DataSources: map[string]*configschema.Block{
19941994
"aws_data_source": {
19951995
Attributes: map[string]*configschema.Attribute{
1996+
"id": {Type: cty.String, Computed: true},
19961997
"foo": {Type: cty.String, Optional: true},
19971998
},
19981999
},
@@ -4992,8 +4993,10 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) {
49924993
}
49934994
}
49944995
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
4996+
cfg := req.Config.AsValueMap()
4997+
cfg["id"] = cty.StringVal("data_id")
49954998
return providers.ReadDataSourceResponse{
4996-
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("ReadDataSource called, but should not have been")),
4999+
State: cty.ObjectVal(cfg),
49975000
}
49985001
}
49995002

@@ -5010,9 +5013,6 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) {
50105013
// thus the plan call below is forced to produce a deferred read action.
50115014

50125015
plan, diags := ctx.Plan()
5013-
if p.ReadDataSourceCalled {
5014-
t.Errorf("ReadDataSource was called on the provider, but should not have been because we didn't refresh")
5015-
}
50165016
if diags.HasErrors() {
50175017
t.Fatalf("unexpected errors: %s", diags.Err())
50185018
}
@@ -5042,38 +5042,30 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) {
50425042
}
50435043
checkVals(t, objectVal(t, schema, map[string]cty.Value{
50445044
"num": cty.StringVal("2"),
5045-
"computed": cty.UnknownVal(cty.String),
5045+
"computed": cty.StringVal("data_id"),
50465046
}), ric.After)
50475047
case "aws_instance.foo[1]":
50485048
if res.Action != plans.Create {
50495049
t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action)
50505050
}
50515051
checkVals(t, objectVal(t, schema, map[string]cty.Value{
50525052
"num": cty.StringVal("2"),
5053-
"computed": cty.UnknownVal(cty.String),
5053+
"computed": cty.StringVal("data_id"),
50545054
}), ric.After)
50555055
case "data.aws_vpc.bar[0]":
50565056
if res.Action != plans.Read {
50575057
t.Fatalf("resource %s should be read, got %s", ric.Addr, ric.Action)
50585058
}
50595059
checkVals(t, objectVal(t, schema, map[string]cty.Value{
5060-
// In a normal flow we would've read an exact value in
5061-
// ReadDataSource, but because this test doesn't run
5062-
// cty.Refresh we have no opportunity to do that lookup
5063-
// and a deferred read is forced.
5064-
"id": cty.UnknownVal(cty.String),
5060+
"id": cty.StringVal("data_id"),
50655061
"foo": cty.StringVal("0"),
50665062
}), ric.After)
50675063
case "data.aws_vpc.bar[1]":
50685064
if res.Action != plans.Read {
50695065
t.Fatalf("resource %s should be read, got %s", ric.Addr, ric.Action)
50705066
}
50715067
checkVals(t, objectVal(t, schema, map[string]cty.Value{
5072-
// In a normal flow we would've read an exact value in
5073-
// ReadDataSource, but because this test doesn't run
5074-
// cty.Refresh we have no opportunity to do that lookup
5075-
// and a deferred read is forced.
5076-
"id": cty.UnknownVal(cty.String),
5068+
"id": cty.StringVal("data_id"),
50775069
"foo": cty.StringVal("1"),
50785070
}), ric.After)
50795071
default:
@@ -5513,11 +5505,18 @@ func TestContext2Plan_invalidOutput(t *testing.T) {
55135505
data "aws_data_source" "name" {}
55145506
55155507
output "out" {
5516-
value = "${data.aws_data_source.name.missing}"
5508+
value = data.aws_data_source.name.missing
55175509
}`,
55185510
})
55195511

55205512
p := testProvider("aws")
5513+
p.ReadDataSourceResponse = providers.ReadDataSourceResponse{
5514+
State: cty.ObjectVal(map[string]cty.Value{
5515+
"id": cty.StringVal("data_id"),
5516+
"foo": cty.StringVal("foo"),
5517+
}),
5518+
}
5519+
55215520
ctx := testContext2(t, &ContextOpts{
55225521
Config: m,
55235522
Providers: map[addrs.Provider]providers.Factory{
@@ -5558,6 +5557,13 @@ resource "aws_instance" "foo" {
55585557
})
55595558

55605559
p := testProvider("aws")
5560+
p.ReadDataSourceResponse = providers.ReadDataSourceResponse{
5561+
State: cty.ObjectVal(map[string]cty.Value{
5562+
"id": cty.StringVal("data_id"),
5563+
"foo": cty.StringVal("foo"),
5564+
}),
5565+
}
5566+
55615567
ctx := testContext2(t, &ContextOpts{
55625568
Config: m,
55635569
Providers: map[addrs.Provider]providers.Factory{

terraform/context_refresh_test.go

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -949,32 +949,11 @@ func TestContext2Refresh_dataState(t *testing.T) {
949949

950950
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
951951
m := req.Config.AsValueMap()
952-
m["inputs"] = cty.MapVal(map[string]cty.Value{"test": cty.StringVal("yes")})
953952
readStateVal = cty.ObjectVal(m)
954953

955954
return providers.ReadDataSourceResponse{
956955
State: readStateVal,
957956
}
958-
959-
// FIXME: should the "outputs" value here be added to the reutnred state?
960-
// Attributes: map[string]*ResourceAttrDiff{
961-
// "inputs.#": {
962-
// Old: "0",
963-
// New: "1",
964-
// Type: DiffAttrInput,
965-
// },
966-
// "inputs.test": {
967-
// Old: "",
968-
// New: "yes",
969-
// Type: DiffAttrInput,
970-
// },
971-
// "outputs.#": {
972-
// Old: "",
973-
// New: "",
974-
// NewComputed: true,
975-
// Type: DiffAttrOutput,
976-
// },
977-
// },
978957
}
979958

980959
s, diags := ctx.Refresh()
@@ -986,14 +965,6 @@ func TestContext2Refresh_dataState(t *testing.T) {
986965
t.Fatal("ReadDataSource should have been called")
987966
}
988967

989-
// mod := s.RootModule()
990-
// if got := mod.Resources["data.null_data_source.testing"].Primary.ID; got != "-" {
991-
// t.Fatalf("resource id is %q; want %s", got, "-")
992-
// }
993-
// if !reflect.DeepEqual(mod.Resources["data.null_data_source.testing"].Primary, p.ReadDataApplyReturn) {
994-
// t.Fatalf("bad: %#v", mod.Resources)
995-
// }
996-
997968
mod := s.RootModule()
998969

999970
newState, err := mod.Resources["data.null_data_source.testing"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType())
@@ -1612,6 +1583,11 @@ func TestContext2Refresh_dataResourceDependsOn(t *testing.T) {
16121583
},
16131584
}
16141585
p.DiffFn = testDiffFn
1586+
p.ReadDataSourceResponse = providers.ReadDataSourceResponse{
1587+
State: cty.ObjectVal(map[string]cty.Value{
1588+
"compute": cty.StringVal("value"),
1589+
}),
1590+
}
16151591

16161592
state := states.NewState()
16171593
root := state.EnsureModule(addrs.RootModuleInstance)

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
},

0 commit comments

Comments
 (0)