mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
terraform: Refresh supports new data sources
This commit is contained in:
parent
9c16489887
commit
38286fe491
@ -233,6 +233,15 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) {
|
|||||||
Validate: opts.Validate,
|
Validate: opts.Validate,
|
||||||
}).Build(RootModulePath)
|
}).Build(RootModulePath)
|
||||||
|
|
||||||
|
case GraphTypeRefresh:
|
||||||
|
return (&RefreshGraphBuilder{
|
||||||
|
Module: c.module,
|
||||||
|
State: c.state,
|
||||||
|
Providers: c.components.ResourceProviders(),
|
||||||
|
Targets: c.targets,
|
||||||
|
Validate: opts.Validate,
|
||||||
|
}).Build(RootModulePath)
|
||||||
|
|
||||||
case GraphTypeLegacy:
|
case GraphTypeLegacy:
|
||||||
return c.graphBuilder(opts).Build(RootModulePath)
|
return c.graphBuilder(opts).Build(RootModulePath)
|
||||||
}
|
}
|
||||||
@ -569,8 +578,15 @@ func (c *Context) Refresh() (*State, error) {
|
|||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.DeepCopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Build the graph
|
// Used throughout below
|
||||||
graph, err := c.Graph(GraphTypeLegacy, nil)
|
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
||||||
|
|
||||||
|
// Build the graph.
|
||||||
|
graphType := GraphTypeLegacy
|
||||||
|
if !X_legacyGraph {
|
||||||
|
graphType = GraphTypeRefresh
|
||||||
|
}
|
||||||
|
graph, err := c.Graph(graphType, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ type GraphType byte
|
|||||||
const (
|
const (
|
||||||
GraphTypeInvalid GraphType = 0
|
GraphTypeInvalid GraphType = 0
|
||||||
GraphTypeLegacy GraphType = iota
|
GraphTypeLegacy GraphType = iota
|
||||||
|
GraphTypeRefresh
|
||||||
GraphTypePlan
|
GraphTypePlan
|
||||||
GraphTypePlanDestroy
|
GraphTypePlanDestroy
|
||||||
GraphTypeApply
|
GraphTypeApply
|
||||||
@ -22,5 +23,6 @@ var GraphTypeMap = map[string]GraphType{
|
|||||||
"apply": GraphTypeApply,
|
"apply": GraphTypeApply,
|
||||||
"plan": GraphTypePlan,
|
"plan": GraphTypePlan,
|
||||||
"plan-destroy": GraphTypePlanDestroy,
|
"plan-destroy": GraphTypePlanDestroy,
|
||||||
|
"refresh": GraphTypeRefresh,
|
||||||
"legacy": GraphTypeLegacy,
|
"legacy": GraphTypeLegacy,
|
||||||
}
|
}
|
||||||
|
@ -520,7 +520,7 @@ func TestContext2Refresh_outputPartial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Refresh_state(t *testing.T) {
|
func TestContext2Refresh_stateBasic(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModule(t, "refresh-basic")
|
m := testModule(t, "refresh-basic")
|
||||||
state := &State{
|
state := &State{
|
||||||
@ -529,6 +529,7 @@ func TestContext2Refresh_state(t *testing.T) {
|
|||||||
Path: rootModulePath,
|
Path: rootModulePath,
|
||||||
Resources: map[string]*ResourceState{
|
Resources: map[string]*ResourceState{
|
||||||
"aws_instance.web": &ResourceState{
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
Primary: &InstanceState{
|
Primary: &InstanceState{
|
||||||
ID: "bar",
|
ID: "bar",
|
||||||
},
|
},
|
||||||
@ -737,6 +738,21 @@ func TestContext2Refresh_unknownProvider(t *testing.T) {
|
|||||||
ctx := testContext2(t, &ContextOpts{
|
ctx := testContext2(t, &ContextOpts{
|
||||||
Module: m,
|
Module: m,
|
||||||
Providers: map[string]ResourceProviderFactory{},
|
Providers: map[string]ResourceProviderFactory{},
|
||||||
|
State: &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, err := ctx.Refresh(); err == nil {
|
if _, err := ctx.Refresh(); err == nil {
|
||||||
|
121
terraform/graph_builder_refresh.go
Normal file
121
terraform/graph_builder_refresh.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RefreshGraphBuilder implements GraphBuilder and is responsible for building
|
||||||
|
// a graph for refreshing (updating the Terraform state).
|
||||||
|
//
|
||||||
|
// The primary difference between this graph and others:
|
||||||
|
//
|
||||||
|
// * Based on the state since it represents the only resources that
|
||||||
|
// need to be refreshed.
|
||||||
|
//
|
||||||
|
// * Ignores lifecycle options since no lifecycle events occur here. This
|
||||||
|
// simplifies the graph significantly since complex transforms such as
|
||||||
|
// create-before-destroy can be completely ignored.
|
||||||
|
//
|
||||||
|
type RefreshGraphBuilder struct {
|
||||||
|
// Module is the root module for the graph to build.
|
||||||
|
Module *module.Tree
|
||||||
|
|
||||||
|
// State is the current state
|
||||||
|
State *State
|
||||||
|
|
||||||
|
// Providers is the list of providers supported.
|
||||||
|
Providers []string
|
||||||
|
|
||||||
|
// Targets are resources to target
|
||||||
|
Targets []string
|
||||||
|
|
||||||
|
// DisableReduce, if true, will not reduce the graph. Great for testing.
|
||||||
|
DisableReduce bool
|
||||||
|
|
||||||
|
// Validate will do structural validation of the graph.
|
||||||
|
Validate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *RefreshGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
|
return (&BasicGraphBuilder{
|
||||||
|
Steps: b.Steps(),
|
||||||
|
Validate: b.Validate,
|
||||||
|
Name: "RefreshGraphBuilder",
|
||||||
|
}).Build(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See GraphBuilder
|
||||||
|
func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
|
||||||
|
// Custom factory for creating providers.
|
||||||
|
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
|
||||||
|
return &NodeApplyableProvider{
|
||||||
|
NodeAbstractProvider: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodeRefreshableResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []GraphTransformer{
|
||||||
|
// Creates all the resources represented in the state
|
||||||
|
&StateTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
State: b.State,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Creates all the data resources that aren't in the state
|
||||||
|
&ConfigTransformer{
|
||||||
|
Concrete: concreteResource,
|
||||||
|
Module: b.Module,
|
||||||
|
Unique: true,
|
||||||
|
ModeFilter: true,
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Attach the state
|
||||||
|
&AttachStateTransformer{State: b.State},
|
||||||
|
|
||||||
|
// Attach the configuration to any resources
|
||||||
|
&AttachResourceConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add root variables
|
||||||
|
&RootVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Create all the providers
|
||||||
|
&MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider},
|
||||||
|
&ProviderTransformer{},
|
||||||
|
&DisableProviderTransformer{},
|
||||||
|
&ParentProviderTransformer{},
|
||||||
|
&AttachProviderConfigTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add the outputs
|
||||||
|
&OutputTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Add module variables
|
||||||
|
&ModuleVariableTransformer{Module: b.Module},
|
||||||
|
|
||||||
|
// Connect so that the references are ready for targeting. We'll
|
||||||
|
// have to connect again later for providers and so on.
|
||||||
|
&ReferenceTransformer{},
|
||||||
|
|
||||||
|
// Target
|
||||||
|
&TargetsTransformer{Targets: b.Targets},
|
||||||
|
|
||||||
|
// Single root
|
||||||
|
&RootTransformer{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.DisableReduce {
|
||||||
|
// Perform the transitive reduction to make our graph a bit
|
||||||
|
// more sane if possible (it usually is possible).
|
||||||
|
steps = append(steps, &TransitiveReductionTransformer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
@ -4,9 +4,9 @@ package terraform
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const _GraphType_name = "GraphTypeInvalidGraphTypeLegacyGraphTypePlanGraphTypePlanDestroyGraphTypeApply"
|
const _GraphType_name = "GraphTypeInvalidGraphTypeLegacyGraphTypeRefreshGraphTypePlanGraphTypePlanDestroyGraphTypeApply"
|
||||||
|
|
||||||
var _GraphType_index = [...]uint8{0, 16, 31, 44, 64, 78}
|
var _GraphType_index = [...]uint8{0, 16, 31, 47, 60, 80, 94}
|
||||||
|
|
||||||
func (i GraphType) String() string {
|
func (i GraphType) String() string {
|
||||||
if i >= GraphType(len(_GraphType_index)-1) {
|
if i >= GraphType(len(_GraphType_index)-1) {
|
||||||
|
222
terraform/node_resource_refresh.go
Normal file
222
terraform/node_resource_refresh.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeRefreshableResource represents a resource that is "applyable":
|
||||||
|
// it is ready to be applied and is represented by a diff.
|
||||||
|
type NodeRefreshableResource struct {
|
||||||
|
*NodeAbstractResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeDestroyer
|
||||||
|
func (n *NodeRefreshableResource) DestroyAddr() *ResourceAddress {
|
||||||
|
return n.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable
|
||||||
|
func (n *NodeRefreshableResource) EvalTree() EvalNode {
|
||||||
|
// Eval info is different depending on what kind of resource this is
|
||||||
|
switch mode := n.Addr.Mode; mode {
|
||||||
|
case config.ManagedResourceMode:
|
||||||
|
return n.evalTreeManagedResource()
|
||||||
|
case config.DataResourceMode:
|
||||||
|
return n.evalTreeDataResource()
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unsupported resource mode %s", mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeRefreshableResource) evalTreeManagedResource() EvalNode {
|
||||||
|
addr := n.NodeAbstractResource.Addr
|
||||||
|
|
||||||
|
// stateId is the ID to put into the state
|
||||||
|
stateId := addr.stateId()
|
||||||
|
|
||||||
|
// Build the instance info. More of this will be populated during eval
|
||||||
|
info := &InstanceInfo{
|
||||||
|
Id: stateId,
|
||||||
|
Type: addr.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare a bunch of variables that are used for state during
|
||||||
|
// evaluation. Most of this are written to by-address below.
|
||||||
|
var provider ResourceProvider
|
||||||
|
var state *InstanceState
|
||||||
|
|
||||||
|
// This happened during initial development. All known cases were
|
||||||
|
// fixed and tested but as a sanity check let's assert here.
|
||||||
|
if n.ResourceState == nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"No resource state attached for addr: %s\n\n"+
|
||||||
|
"This is a bug. Please report this to Terraform with your configuration\n"+
|
||||||
|
"and state attached. Please be careful to scrub any sensitive information.",
|
||||||
|
addr)
|
||||||
|
return &EvalReturnError{Error: &err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
&EvalReadState{
|
||||||
|
Name: stateId,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalRefresh{
|
||||||
|
Info: info,
|
||||||
|
Provider: &provider,
|
||||||
|
State: &state,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: n.ResourceState.Type,
|
||||||
|
Provider: n.ResourceState.Provider,
|
||||||
|
Dependencies: n.ResourceState.Dependencies,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeRefreshableResource) evalTreeDataResource() EvalNode {
|
||||||
|
addr := n.NodeAbstractResource.Addr
|
||||||
|
|
||||||
|
// stateId is the ID to put into the state
|
||||||
|
stateId := addr.stateId()
|
||||||
|
|
||||||
|
// Build the instance info. More of this will be populated during eval
|
||||||
|
info := &InstanceInfo{
|
||||||
|
Id: stateId,
|
||||||
|
Type: addr.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the state if we have it, if not we build it
|
||||||
|
rs := n.ResourceState
|
||||||
|
if rs == nil {
|
||||||
|
rs = &ResourceState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the config isn't empty we update the state
|
||||||
|
if n.Config != nil {
|
||||||
|
// Determine the dependencies for the state. We use some older
|
||||||
|
// code for this that we've used for a long time.
|
||||||
|
var stateDeps []string
|
||||||
|
{
|
||||||
|
oldN := &graphNodeExpandedResource{
|
||||||
|
Resource: n.Config,
|
||||||
|
Index: addr.Index,
|
||||||
|
}
|
||||||
|
stateDeps = oldN.StateDependencies()
|
||||||
|
}
|
||||||
|
|
||||||
|
rs = &ResourceState{
|
||||||
|
Type: n.Config.Type,
|
||||||
|
Provider: n.Config.Provider,
|
||||||
|
Dependencies: stateDeps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the resource for eval
|
||||||
|
resource := &Resource{
|
||||||
|
Name: addr.Name,
|
||||||
|
Type: addr.Type,
|
||||||
|
CountIndex: addr.Index,
|
||||||
|
}
|
||||||
|
if resource.CountIndex < 0 {
|
||||||
|
resource.CountIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare a bunch of variables that are used for state during
|
||||||
|
// evaluation. Most of this are written to by-address below.
|
||||||
|
var config *ResourceConfig
|
||||||
|
var diff *InstanceDiff
|
||||||
|
var provider ResourceProvider
|
||||||
|
var state *InstanceState
|
||||||
|
|
||||||
|
return &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
// Always destroy the existing state first, since we must
|
||||||
|
// make sure that values from a previous read will not
|
||||||
|
// get interpolated if we end up needing to defer our
|
||||||
|
// loading until apply time.
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: rs.Type,
|
||||||
|
Provider: rs.Provider,
|
||||||
|
Dependencies: rs.Dependencies,
|
||||||
|
State: &state, // state is nil here
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalInterpolate{
|
||||||
|
Config: n.Config.RawConfig.Copy(),
|
||||||
|
Resource: resource,
|
||||||
|
Output: &config,
|
||||||
|
},
|
||||||
|
|
||||||
|
// The rest of this pass can proceed only if there are no
|
||||||
|
// computed values in our config.
|
||||||
|
// (If there are, we'll deal with this during the plan and
|
||||||
|
// apply phases.)
|
||||||
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the config explicitly has a depends_on for this
|
||||||
|
// data source, assume the intention is to prevent
|
||||||
|
// refreshing ahead of that dependency.
|
||||||
|
if len(n.Config.DependsOn) > 0 {
|
||||||
|
return true, EvalEarlyExitError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
Then: EvalNoop{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// The remainder of this pass is the same as running
|
||||||
|
// a "plan" pass immediately followed by an "apply" pass,
|
||||||
|
// populating the state early so it'll be available to
|
||||||
|
// provider configurations that need this data during
|
||||||
|
// refresh/plan.
|
||||||
|
&EvalGetProvider{
|
||||||
|
Name: n.ProvidedBy()[0],
|
||||||
|
Output: &provider,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalReadDataDiff{
|
||||||
|
Info: info,
|
||||||
|
Config: &config,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &diff,
|
||||||
|
OutputState: &state,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalReadDataApply{
|
||||||
|
Info: info,
|
||||||
|
Diff: &diff,
|
||||||
|
Provider: &provider,
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalWriteState{
|
||||||
|
Name: stateId,
|
||||||
|
ResourceType: rs.Type,
|
||||||
|
Provider: rs.Provider,
|
||||||
|
Dependencies: rs.Dependencies,
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
|
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -45,8 +45,10 @@ func (t *AttachStateTransformer) Transform(g *Graph) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach the first resource state we get
|
// Attach the first resource state we get
|
||||||
|
log.Printf("SEARCH: %s", addr)
|
||||||
found := false
|
found := false
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
log.Printf("WTF: %s %#v", addr, result)
|
||||||
if rs, ok := result.Value.(*ResourceState); ok {
|
if rs, ok := result.Value.(*ResourceState); ok {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[DEBUG] Attaching resource state to %q: %s",
|
"[DEBUG] Attaching resource state to %q: %s",
|
||||||
|
Loading…
Reference in New Issue
Block a user