mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
96b099cf35
The validation for ignore_changes was too broad, and makes it difficult to ignore changes in an attribute that the user does not want to set. While the goal of ignore_changes is to prevent changes in the configuration alone, we don't intend to break the use-case of ignoring drift from the provider. Since we cannot easily narrow the validation to only detect computed attributes at the moment, we can drop this error altogether for now.
166 lines
5.2 KiB
Go
166 lines
5.2 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/configs"
|
|
"github.com/hashicorp/terraform/plans/objchange"
|
|
"github.com/hashicorp/terraform/providers"
|
|
"github.com/hashicorp/terraform/states"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
)
|
|
|
|
// EvalRefresh is an EvalNode implementation that does a refresh for
|
|
// a resource.
|
|
type EvalRefresh struct {
|
|
Addr addrs.ResourceInstance
|
|
ProviderAddr addrs.AbsProviderConfig
|
|
Provider *providers.Interface
|
|
ProviderMetas map[addrs.Provider]*configs.ProviderMeta
|
|
ProviderSchema **ProviderSchema
|
|
State **states.ResourceInstanceObject
|
|
Output **states.ResourceInstanceObject
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalRefresh) Eval(ctx EvalContext) tfdiags.Diagnostics {
|
|
state := *n.State
|
|
absAddr := n.Addr.Absolute(ctx.Path())
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
// If we have no state, we don't do any refreshing
|
|
if state == nil {
|
|
log.Printf("[DEBUG] refresh: %s: no state, so not refreshing", n.Addr.Absolute(ctx.Path()))
|
|
return diags
|
|
}
|
|
|
|
schema, _ := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
|
|
if schema == nil {
|
|
// Should be caught during validation, so we don't bother with a pretty error here
|
|
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type))
|
|
return diags
|
|
}
|
|
|
|
metaConfigVal := cty.NullVal(cty.DynamicPseudoType)
|
|
if n.ProviderMetas != nil {
|
|
if m, ok := n.ProviderMetas[n.ProviderAddr.Provider]; ok && m != nil {
|
|
log.Printf("[DEBUG] EvalRefresh: ProviderMeta config value set")
|
|
// if the provider doesn't support this feature, throw an error
|
|
if (*n.ProviderSchema).ProviderMeta == nil {
|
|
log.Printf("[DEBUG] EvalRefresh: no ProviderMeta schema")
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: fmt.Sprintf("Provider %s doesn't support provider_meta", n.ProviderAddr.Provider.String()),
|
|
Detail: fmt.Sprintf("The resource %s belongs to a provider that doesn't support provider_meta blocks", n.Addr),
|
|
Subject: &m.ProviderRange,
|
|
})
|
|
} else {
|
|
log.Printf("[DEBUG] EvalRefresh: ProviderMeta schema found: %+v", (*n.ProviderSchema).ProviderMeta)
|
|
var configDiags tfdiags.Diagnostics
|
|
metaConfigVal, _, configDiags = ctx.EvaluateBlock(m.Config, (*n.ProviderSchema).ProviderMeta, nil, EvalDataForNoInstanceKey)
|
|
diags = diags.Append(configDiags)
|
|
if configDiags.HasErrors() {
|
|
return diags
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call pre-refresh hook
|
|
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PreRefresh(absAddr, states.CurrentGen, state.Value)
|
|
}))
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
// Refresh!
|
|
priorVal := state.Value
|
|
|
|
// Unmarked before sending to provider
|
|
var priorPaths []cty.PathValueMarks
|
|
if priorVal.ContainsMarked() {
|
|
priorVal, priorPaths = priorVal.UnmarkDeepWithPaths()
|
|
}
|
|
|
|
req := providers.ReadResourceRequest{
|
|
TypeName: n.Addr.Resource.Type,
|
|
PriorState: priorVal,
|
|
Private: state.Private,
|
|
ProviderMeta: metaConfigVal,
|
|
}
|
|
|
|
provider := *n.Provider
|
|
resp := provider.ReadResource(req)
|
|
diags = diags.Append(resp.Diagnostics)
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
if resp.NewState == cty.NilVal {
|
|
// This ought not to happen in real cases since it's not possible to
|
|
// send NilVal over the plugin RPC channel, but it can come up in
|
|
// tests due to sloppy mocking.
|
|
panic("new state is cty.NilVal")
|
|
}
|
|
|
|
for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) {
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Provider produced invalid object",
|
|
fmt.Sprintf(
|
|
"Provider %q planned an invalid value for %s during refresh: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
|
|
n.ProviderAddr.Provider.String(), absAddr, tfdiags.FormatError(err),
|
|
),
|
|
))
|
|
}
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
// We have no way to exempt provider using the legacy SDK from this check,
|
|
// so we can only log inconsistencies with the updated state values.
|
|
// In most cases these are not errors anyway, and represent "drift" from
|
|
// external changes which will be handled by the subsequent plan.
|
|
if errs := objchange.AssertObjectCompatible(schema, priorVal, resp.NewState); len(errs) > 0 {
|
|
var buf strings.Builder
|
|
fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s during refresh.", n.ProviderAddr.Provider.String(), absAddr)
|
|
for _, err := range errs {
|
|
fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err))
|
|
}
|
|
log.Print(buf.String())
|
|
}
|
|
|
|
newState := state.DeepCopy()
|
|
newState.Value = resp.NewState
|
|
newState.Private = resp.Private
|
|
newState.Dependencies = state.Dependencies
|
|
newState.CreateBeforeDestroy = state.CreateBeforeDestroy
|
|
|
|
// Call post-refresh hook
|
|
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostRefresh(absAddr, states.CurrentGen, priorVal, newState.Value)
|
|
}))
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
// Mark the value if necessary
|
|
if len(priorPaths) > 0 {
|
|
newState.Value = newState.Value.MarkWithPaths(priorPaths)
|
|
}
|
|
|
|
if n.Output != nil {
|
|
*n.Output = newState
|
|
}
|
|
|
|
return diags
|
|
}
|