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:
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be created"), dispAddr))
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:
switch language {
case DiffLanguageProposedChange:
@@ -166,7 +172,7 @@ func ResourceChange(
))
case addrs.DataResourceMode:
buf.WriteString(fmt.Sprintf(
"data %q %q ",
"data %q %q",
addr.Resource.Resource.Type,
addr.Resource.Resource.Name,
))

View File

@@ -532,6 +532,70 @@ new line
+ forced = "example" # forces replacement
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": {

View File

@@ -405,6 +405,10 @@ func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeS
r.ActionReason = "delete_because_each_key"
case plans.ResourceInstanceDeleteBecauseNoModule:
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:
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"
ReasonDeleteBecauseEachKey ChangeReason = "delete_because_each_key"
ReasonDeleteBecauseNoModule ChangeReason = "delete_because_no_module"
ReasonReadBecauseConfigUnknown ChangeReason = "read_because_config_unknown"
ReasonReadBecauseDependencyPending ChangeReason = "read_because_dependency_pending"
)
func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason {
@@ -104,6 +106,10 @@ func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason
return ReasonDeleteBecauseEachKey
case plans.ResourceInstanceDeleteBecauseNoModule:
return ReasonDeleteBecauseNoModule
case plans.ResourceInstanceReadBecauseConfigUnknown:
return ReasonReadBecauseConfigUnknown
case plans.ResourceInstanceReadBecauseDependencyPending:
return ReasonReadBecauseDependencyPending
default:
// This should never happen, but there's no good way to guarantee
// exhaustive handling of the enum, so a generic fall back is better