core: Report reason for deferring data read until apply

We have two different reasons why a data resource might be read only
during apply, rather than during planning as usual: the configuration
contains unknown values, or the data resource as a whole depends on a
managed resource which itself has a change pending.

However, we didn't previously distinguish these two in a way that allowed
the UI to describe the difference, and so we confusingly reported both
as "config refers to values not yet known", which in turn led to a number
of reasonable questions about why Terraform was claiming that but then
immediately below showing the configuration entirely known.

Now we'll use our existing "ActionReason" mechanism to tell the UI layer
which of the two reasons applies to a particular data resource instance.
The "dependency pending" situation tends to happen in conjunction with
"config unknown", so we'll prefer to refer that the configuration is
unknown if both are true.
This commit is contained in:
Martin Atkins 2022-04-29 17:28:43 -07:00
parent 98f9d646ce
commit 4cffff24b1
12 changed files with 205 additions and 47 deletions

View File

@ -71,7 +71,13 @@ func ResourceChange(
case plans.Create: case plans.Create:
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be created"), dispAddr)) buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be created"), dispAddr))
case plans.Read: case plans.Read:
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be read during apply\n # (config refers to values not yet known)"), dispAddr)) buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be read during apply"), dispAddr))
switch change.ActionReason {
case plans.ResourceInstanceReadBecauseConfigUnknown:
buf.WriteString("\n # (config refers to values not yet known)")
case plans.ResourceInstanceReadBecauseDependencyPending:
buf.WriteString("\n # (depends on a resource or a module with changes pending)")
}
case plans.Update: case plans.Update:
switch language { switch language {
case DiffLanguageProposedChange: case DiffLanguageProposedChange:
@ -166,7 +172,7 @@ func ResourceChange(
)) ))
case addrs.DataResourceMode: case addrs.DataResourceMode:
buf.WriteString(fmt.Sprintf( buf.WriteString(fmt.Sprintf(
"data %q %q ", "data %q %q",
addr.Resource.Resource.Type, addr.Resource.Resource.Type,
addr.Resource.Resource.Name, addr.Resource.Resource.Name,
)) ))

View File

@ -532,6 +532,70 @@ new line
+ forced = "example" # forces replacement + forced = "example" # forces replacement
name = "name" name = "name"
} }
`,
},
"read during apply because of unknown configuration": {
Action: plans.Read,
ActionReason: plans.ResourceInstanceReadBecauseConfigUnknown,
Mode: addrs.DataResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("name"),
}),
After: cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("name"),
}),
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"name": {Type: cty.String, Optional: true},
},
},
ExpectedOutput: ` # data.test_instance.example will be read during apply
# (config refers to values not yet known)
<= data "test_instance" "example" {
name = "name"
}
`,
},
"read during apply because of pending changes to upstream dependency": {
Action: plans.Read,
ActionReason: plans.ResourceInstanceReadBecauseDependencyPending,
Mode: addrs.DataResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("name"),
}),
After: cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("name"),
}),
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"name": {Type: cty.String, Optional: true},
},
},
ExpectedOutput: ` # data.test_instance.example will be read during apply
# (depends on a resource or a module with changes pending)
<= data "test_instance" "example" {
name = "name"
}
`,
},
"read during apply for unspecified reason": {
Action: plans.Read,
Mode: addrs.DataResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("name"),
}),
After: cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("name"),
}),
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"name": {Type: cty.String, Optional: true},
},
},
ExpectedOutput: ` # data.test_instance.example will be read during apply
<= data "test_instance" "example" {
name = "name"
}
`, `,
}, },
"show all identifying attributes even if unchanged": { "show all identifying attributes even if unchanged": {

View File

@ -405,6 +405,10 @@ func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeS
r.ActionReason = "delete_because_each_key" r.ActionReason = "delete_because_each_key"
case plans.ResourceInstanceDeleteBecauseNoModule: case plans.ResourceInstanceDeleteBecauseNoModule:
r.ActionReason = "delete_because_no_module" r.ActionReason = "delete_because_no_module"
case plans.ResourceInstanceReadBecauseConfigUnknown:
r.ActionReason = "read_because_config_unknown"
case plans.ResourceInstanceReadBecauseDependencyPending:
r.ActionReason = "read_because_dependency_pending"
default: default:
return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason) return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason)
} }

View File

@ -80,6 +80,8 @@ const (
ReasonDeleteBecauseCountIndex ChangeReason = "delete_because_count_index" ReasonDeleteBecauseCountIndex ChangeReason = "delete_because_count_index"
ReasonDeleteBecauseEachKey ChangeReason = "delete_because_each_key" ReasonDeleteBecauseEachKey ChangeReason = "delete_because_each_key"
ReasonDeleteBecauseNoModule ChangeReason = "delete_because_no_module" ReasonDeleteBecauseNoModule ChangeReason = "delete_because_no_module"
ReasonReadBecauseConfigUnknown ChangeReason = "read_because_config_unknown"
ReasonReadBecauseDependencyPending ChangeReason = "read_because_dependency_pending"
) )
func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason { func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason {
@ -104,6 +106,10 @@ func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason
return ReasonDeleteBecauseEachKey return ReasonDeleteBecauseEachKey
case plans.ResourceInstanceDeleteBecauseNoModule: case plans.ResourceInstanceDeleteBecauseNoModule:
return ReasonDeleteBecauseNoModule return ReasonDeleteBecauseNoModule
case plans.ResourceInstanceReadBecauseConfigUnknown:
return ReasonReadBecauseConfigUnknown
case plans.ResourceInstanceReadBecauseDependencyPending:
return ReasonReadBecauseDependencyPending
default: default:
// This should never happen, but there's no good way to guarantee // This should never happen, but there's no good way to guarantee
// exhaustive handling of the enum, so a generic fall back is better // exhaustive handling of the enum, so a generic fall back is better

View File

@ -407,6 +407,18 @@ const (
// potentially multiple nested modules could all contribute conflicting // potentially multiple nested modules could all contribute conflicting
// specific reasons for a particular instance to no longer be declared. // specific reasons for a particular instance to no longer be declared.
ResourceInstanceDeleteBecauseNoModule ResourceInstanceChangeActionReason = 'M' ResourceInstanceDeleteBecauseNoModule ResourceInstanceChangeActionReason = 'M'
// ResourceInstanceReadBecauseConfigUnknown indicates that the resource
// must be read during apply (rather than during planning) because its
// configuration contains unknown values. This reason applies only to
// data resources.
ResourceInstanceReadBecauseConfigUnknown ResourceInstanceChangeActionReason = '?'
// ResourceInstanceReadBecauseDependencyPending indicates that the resource
// must be read during apply (rather than during planning) because it
// depends on a managed resource instance which has its own changes
// pending.
ResourceInstanceReadBecauseDependencyPending ResourceInstanceChangeActionReason = '!'
) )
// OutputChange describes a change to an output value. // OutputChange describes a change to an output value.

View File

@ -150,21 +150,25 @@ const (
ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY ResourceInstanceActionReason = 7 ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY ResourceInstanceActionReason = 7
ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE ResourceInstanceActionReason = 8 ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE ResourceInstanceActionReason = 8
ResourceInstanceActionReason_REPLACE_BY_TRIGGERS ResourceInstanceActionReason = 9 ResourceInstanceActionReason_REPLACE_BY_TRIGGERS ResourceInstanceActionReason = 9
ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN ResourceInstanceActionReason = 10
ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING ResourceInstanceActionReason = 11
) )
// Enum value maps for ResourceInstanceActionReason. // Enum value maps for ResourceInstanceActionReason.
var ( var (
ResourceInstanceActionReason_name = map[int32]string{ ResourceInstanceActionReason_name = map[int32]string{
0: "NONE", 0: "NONE",
1: "REPLACE_BECAUSE_TAINTED", 1: "REPLACE_BECAUSE_TAINTED",
2: "REPLACE_BY_REQUEST", 2: "REPLACE_BY_REQUEST",
3: "REPLACE_BECAUSE_CANNOT_UPDATE", 3: "REPLACE_BECAUSE_CANNOT_UPDATE",
4: "DELETE_BECAUSE_NO_RESOURCE_CONFIG", 4: "DELETE_BECAUSE_NO_RESOURCE_CONFIG",
5: "DELETE_BECAUSE_WRONG_REPETITION", 5: "DELETE_BECAUSE_WRONG_REPETITION",
6: "DELETE_BECAUSE_COUNT_INDEX", 6: "DELETE_BECAUSE_COUNT_INDEX",
7: "DELETE_BECAUSE_EACH_KEY", 7: "DELETE_BECAUSE_EACH_KEY",
8: "DELETE_BECAUSE_NO_MODULE", 8: "DELETE_BECAUSE_NO_MODULE",
9: "REPLACE_BY_TRIGGERS", 9: "REPLACE_BY_TRIGGERS",
10: "READ_BECAUSE_CONFIG_UNKNOWN",
11: "READ_BECAUSE_DEPENDENCY_PENDING",
} }
ResourceInstanceActionReason_value = map[string]int32{ ResourceInstanceActionReason_value = map[string]int32{
"NONE": 0, "NONE": 0,
@ -177,6 +181,8 @@ var (
"DELETE_BECAUSE_EACH_KEY": 7, "DELETE_BECAUSE_EACH_KEY": 7,
"DELETE_BECAUSE_NO_MODULE": 8, "DELETE_BECAUSE_NO_MODULE": 8,
"REPLACE_BY_TRIGGERS": 9, "REPLACE_BY_TRIGGERS": 9,
"READ_BECAUSE_CONFIG_UNKNOWN": 10,
"READ_BECAUSE_DEPENDENCY_PENDING": 11,
} }
) )
@ -1300,7 +1306,7 @@ var file_planfile_proto_rawDesc = []byte{
0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48,
0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43,
0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54,
0x45, 0x10, 0x07, 0x2a, 0xc0, 0x02, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x10, 0x07, 0x2a, 0x86, 0x03, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b,
0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53,
@ -1320,18 +1326,23 @@ var file_planfile_proto_rawDesc = []byte{
0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53,
0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x12, 0x17, 0x0a, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x12, 0x17, 0x0a,
0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x52, 0x49, 0x47, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x52, 0x49, 0x47,
0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x2a, 0x6c, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42,
0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x55, 0x4e,
0x49, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x41, 0x44, 0x5f,
0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x45, 0x4e, 0x44, 0x45, 0x4e,
0x1a, 0x0a, 0x16, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x0b, 0x2a, 0x6c, 0x0a, 0x0d,
0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x4f, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a,
0x55, 0x54, 0x50, 0x55, 0x54, 0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45,
0x4f, 0x4e, 0x10, 0x03, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54,
0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43,
0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10,
0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x5f, 0x50, 0x52, 0x45, 0x43,
0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View File

@ -147,6 +147,8 @@ enum ResourceInstanceActionReason {
DELETE_BECAUSE_EACH_KEY = 7; DELETE_BECAUSE_EACH_KEY = 7;
DELETE_BECAUSE_NO_MODULE = 8; DELETE_BECAUSE_NO_MODULE = 8;
REPLACE_BY_TRIGGERS = 9; REPLACE_BY_TRIGGERS = 9;
READ_BECAUSE_CONFIG_UNKNOWN = 10;
READ_BECAUSE_DEPENDENCY_PENDING = 11;
} }
message ResourceInstanceChange { message ResourceInstanceChange {

View File

@ -278,6 +278,10 @@ func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*pla
ret.ActionReason = plans.ResourceInstanceDeleteBecauseEachKey ret.ActionReason = plans.ResourceInstanceDeleteBecauseEachKey
case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE: case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE:
ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoModule ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoModule
case planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN:
ret.ActionReason = plans.ResourceInstanceReadBecauseConfigUnknown
case planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING:
ret.ActionReason = plans.ResourceInstanceReadBecauseDependencyPending
default: default:
return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason) return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason)
} }
@ -625,6 +629,10 @@ func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto
ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY
case plans.ResourceInstanceDeleteBecauseNoModule: case plans.ResourceInstanceDeleteBecauseNoModule:
ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE
case plans.ResourceInstanceReadBecauseConfigUnknown:
ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN
case plans.ResourceInstanceReadBecauseDependencyPending:
ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING
default: default:
return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason) return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason)
} }

View File

@ -18,38 +18,46 @@ func _() {
_ = x[ResourceInstanceDeleteBecauseCountIndex-67] _ = x[ResourceInstanceDeleteBecauseCountIndex-67]
_ = x[ResourceInstanceDeleteBecauseEachKey-69] _ = x[ResourceInstanceDeleteBecauseEachKey-69]
_ = x[ResourceInstanceDeleteBecauseNoModule-77] _ = x[ResourceInstanceDeleteBecauseNoModule-77]
_ = x[ResourceInstanceReadBecauseConfigUnknown-63]
_ = x[ResourceInstanceReadBecauseDependencyPending-33]
} }
const ( const (
_ResourceInstanceChangeActionReason_name_0 = "ResourceInstanceChangeNoReason" _ResourceInstanceChangeActionReason_name_0 = "ResourceInstanceChangeNoReason"
_ResourceInstanceChangeActionReason_name_1 = "ResourceInstanceDeleteBecauseCountIndexResourceInstanceReplaceByTriggersResourceInstanceDeleteBecauseEachKeyResourceInstanceReplaceBecauseCannotUpdate" _ResourceInstanceChangeActionReason_name_1 = "ResourceInstanceReadBecauseDependencyPending"
_ResourceInstanceChangeActionReason_name_2 = "ResourceInstanceDeleteBecauseNoModuleResourceInstanceDeleteBecauseNoResourceConfig" _ResourceInstanceChangeActionReason_name_2 = "ResourceInstanceReadBecauseConfigUnknown"
_ResourceInstanceChangeActionReason_name_3 = "ResourceInstanceReplaceByRequest" _ResourceInstanceChangeActionReason_name_3 = "ResourceInstanceDeleteBecauseCountIndexResourceInstanceReplaceByTriggersResourceInstanceDeleteBecauseEachKeyResourceInstanceReplaceBecauseCannotUpdate"
_ResourceInstanceChangeActionReason_name_4 = "ResourceInstanceReplaceBecauseTainted" _ResourceInstanceChangeActionReason_name_4 = "ResourceInstanceDeleteBecauseNoModuleResourceInstanceDeleteBecauseNoResourceConfig"
_ResourceInstanceChangeActionReason_name_5 = "ResourceInstanceDeleteBecauseWrongRepetition" _ResourceInstanceChangeActionReason_name_5 = "ResourceInstanceReplaceByRequest"
_ResourceInstanceChangeActionReason_name_6 = "ResourceInstanceReplaceBecauseTainted"
_ResourceInstanceChangeActionReason_name_7 = "ResourceInstanceDeleteBecauseWrongRepetition"
) )
var ( var (
_ResourceInstanceChangeActionReason_index_1 = [...]uint8{0, 39, 72, 108, 150} _ResourceInstanceChangeActionReason_index_3 = [...]uint8{0, 39, 72, 108, 150}
_ResourceInstanceChangeActionReason_index_2 = [...]uint8{0, 37, 82} _ResourceInstanceChangeActionReason_index_4 = [...]uint8{0, 37, 82}
) )
func (i ResourceInstanceChangeActionReason) String() string { func (i ResourceInstanceChangeActionReason) String() string {
switch { switch {
case i == 0: case i == 0:
return _ResourceInstanceChangeActionReason_name_0 return _ResourceInstanceChangeActionReason_name_0
case i == 33:
return _ResourceInstanceChangeActionReason_name_1
case i == 63:
return _ResourceInstanceChangeActionReason_name_2
case 67 <= i && i <= 70: case 67 <= i && i <= 70:
i -= 67 i -= 67
return _ResourceInstanceChangeActionReason_name_1[_ResourceInstanceChangeActionReason_index_1[i]:_ResourceInstanceChangeActionReason_index_1[i+1]] return _ResourceInstanceChangeActionReason_name_3[_ResourceInstanceChangeActionReason_index_3[i]:_ResourceInstanceChangeActionReason_index_3[i+1]]
case 77 <= i && i <= 78: case 77 <= i && i <= 78:
i -= 77 i -= 77
return _ResourceInstanceChangeActionReason_name_2[_ResourceInstanceChangeActionReason_index_2[i]:_ResourceInstanceChangeActionReason_index_2[i+1]] return _ResourceInstanceChangeActionReason_name_4[_ResourceInstanceChangeActionReason_index_4[i]:_ResourceInstanceChangeActionReason_index_4[i+1]]
case i == 82: case i == 82:
return _ResourceInstanceChangeActionReason_name_3
case i == 84:
return _ResourceInstanceChangeActionReason_name_4
case i == 87:
return _ResourceInstanceChangeActionReason_name_5 return _ResourceInstanceChangeActionReason_name_5
case i == 84:
return _ResourceInstanceChangeActionReason_name_6
case i == 87:
return _ResourceInstanceChangeActionReason_name_7
default: default:
return "ResourceInstanceChangeActionReason(" + strconv.FormatInt(int64(i), 10) + ")" return "ResourceInstanceChangeActionReason(" + strconv.FormatInt(int64(i), 10) + ")"
} }

View File

@ -1786,6 +1786,9 @@ func TestContext2Plan_computedDataResource(t *testing.T) {
}), }),
rc.After, rc.After,
) )
if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseConfigUnknown; got != want {
t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want)
}
} }
func TestContext2Plan_computedInFunction(t *testing.T) { func TestContext2Plan_computedInFunction(t *testing.T) {
@ -1987,6 +1990,10 @@ func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseConfigUnknown; got != want {
t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want)
}
// foo should now be unknown // foo should now be unknown
foo := rc.After.GetAttr("foo") foo := rc.After.GetAttr("foo")
if foo.IsKnown() { if foo.IsKnown() {
@ -6295,8 +6302,21 @@ data "test_data_source" "e" {
}, },
}) })
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
assertNoErrors(t, diags) assertNoErrors(t, diags)
rc := plan.Changes.ResourceInstance(addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test_data_source",
Name: "d",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
if rc != nil {
if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseDependencyPending; got != want {
t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want)
}
} else {
t.Error("no change for test_data_source.e")
}
} }
func TestContext2Plan_skipRefresh(t *testing.T) { func TestContext2Plan_skipRefresh(t *testing.T) {

View File

@ -1525,15 +1525,23 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths() unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths()
configKnown := configVal.IsWhollyKnown() configKnown := configVal.IsWhollyKnown()
depsPending := n.dependenciesHavePendingChanges(ctx)
// If our configuration contains any unknown values, or we depend on any // If our configuration contains any unknown values, or we depend on any
// unknown values then we must defer the read to the apply phase by // unknown values then we must defer the read to the apply phase by
// producing a "Read" change for this resource, and a placeholder value for // producing a "Read" change for this resource, and a placeholder value for
// it in the state. // it in the state.
if n.forcePlanReadData(ctx) || !configKnown { if depsPending || !configKnown {
if configKnown { var reason plans.ResourceInstanceChangeActionReason
log.Printf("[TRACE] planDataSource: %s configuration is fully known, but we're forcing a read plan to be created", n.Addr) switch {
} else { case !configKnown:
log.Printf("[TRACE] planDataSource: %s configuration not fully known yet, so deferring to apply phase", n.Addr) log.Printf("[TRACE] planDataSource: %s configuration not fully known yet, so deferring to apply phase", n.Addr)
reason = plans.ResourceInstanceReadBecauseConfigUnknown
case depsPending:
// NOTE: depsPending can be true at the same time as configKnown
// is false; configKnown takes precedence because it's more
// specific.
log.Printf("[TRACE] planDataSource: %s configuration is fully known, at least one dependency has changes pending", n.Addr)
reason = plans.ResourceInstanceReadBecauseDependencyPending
} }
proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal) proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
@ -1550,6 +1558,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
Before: priorVal, Before: priorVal,
After: proposedNewVal, After: proposedNewVal,
}, },
ActionReason: reason,
} }
plannedNewState := &states.ResourceInstanceObject{ plannedNewState := &states.ResourceInstanceObject{
@ -1580,10 +1589,11 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
return nil, plannedNewState, keyData, diags return nil, plannedNewState, keyData, diags
} }
// forcePlanReadData determines if we need to override the usual behavior of // dependenciesHavePendingChanges determines whether any managed resource the
// immediately reading from the data source where possible, instead forcing us // receiver depends on has a change pending in the plan, in which case we'd
// to generate a plan. // need to override the usual behavior of immediately reading from the data
func (n *NodeAbstractResourceInstance) forcePlanReadData(ctx EvalContext) bool { // source where possible, and instead defer the read until the apply step.
func (n *NodeAbstractResourceInstance) dependenciesHavePendingChanges(ctx EvalContext) bool {
nModInst := n.Addr.Module nModInst := n.Addr.Module
nMod := nModInst.Module() nMod := nModInst.Module()

View File

@ -173,6 +173,13 @@ For ease of consumption by callers, the plan representation includes a partial r
// - "delete_because_each_key": The corresponding resource uses for_each, // - "delete_because_each_key": The corresponding resource uses for_each,
// but the instance key doesn't match any of the keys in the // but the instance key doesn't match any of the keys in the
// currently-configured for_each value. // currently-configured for_each value.
// - "read_because_config_unknown": For a data resource, Terraform cannot
// read the data during the plan phase because of values in the
// configuration that won't be known until the apply phase.
// - "read_because_dependency_pending": For a data resource, Terraform
// cannot read the data during the plan phase because the data
// resource depends on at least one managed resource that also has
// a pending change in the same plan.
// //
// If there is no special reason to note, Terraform will omit this // If there is no special reason to note, Terraform will omit this
// property altogether. // property altogether.