Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
小 白蛋
Terraform
Commits
b4faa6c9
Commit
b4faa6c9
authored
6 years ago
by
Martin Atkins
Browse files
Options
Download
Email Patches
Plain Diff
core: start of properly planning changes to attributes
parent
10388beb
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
terraform/context_apply_test.go
+1
-0
terraform/context_apply_test.go
terraform/eval_output.go
+250
-36
terraform/eval_output.go
terraform/graph_builder_apply.go
+5
-1
terraform/graph_builder_apply.go
terraform/graph_builder_plan.go
+6
-8
terraform/graph_builder_plan.go
terraform/node_output.go
+158
-13
terraform/node_output.go
terraform/transform_orphan_output.go
+0
-60
terraform/transform_orphan_output.go
terraform/transform_output.go
+37
-47
terraform/transform_output.go
with
457 additions
and
165 deletions
+457
-165
terraform/context_apply_test.go
+
1
-
0
View file @
b4faa6c9
...
...
@@ -216,6 +216,7 @@ func TestContext2Apply_resourceCountZeroList(t *testing.T) {
t
.
Fatalf
(
"diags: %s"
,
diags
.
Err
())
}
t
.
Logf
(
spew
.
Sdump
(
state
))
actual
:=
strings
.
TrimSpace
(
state
.
String
())
expected
:=
strings
.
TrimSpace
(
`<no state>
Outputs:
...
...
This diff is collapsed.
Click to expand it.
terraform/eval_output.go
+
250
-
36
View file @
b4faa6c9
package
terraform
import
(
"fmt"
"log"
"github.com/hashicorp/hcl2/hcl"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
)
// Eval
Delete
Output is an EvalNode implementation that
deletes an output
// f
rom the
state.
type
Eval
Delete
Output
struct
{
// Eval
Read
Output
State
is an EvalNode implementation that
reads the state
// f
or a particular output value from the overall
state.
type
Eval
Read
Output
State
struct
{
Addr
addrs
.
OutputValue
// Output, if non-nil, will have the current state for the requested
// output assigned to its referent.
Output
**
states
.
OutputValue
}
// TODO: test
func
(
n
*
EvalDeleteOutput
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
func
(
n
*
EvalReadOutputState
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
addr
:=
n
.
Addr
.
Absolute
(
ctx
.
Path
())
state
:=
ctx
.
State
()
if
state
==
nil
{
return
nil
,
nil
os
:=
state
.
OutputValue
(
addr
)
if
os
!=
nil
{
if
os
.
Sensitive
{
log
.
Printf
(
"[TRACE] EvalReadOutputState: %s is sensitive"
,
addr
)
}
else
{
log
.
Printf
(
"[TRACE] EvalReadOutputState: %s has stored value %#v"
,
addr
,
os
.
Value
)
}
}
else
{
log
.
Printf
(
"[TRACE] EvalReadOutputState: %s is not yet set"
,
addr
)
}
state
.
RemoveOutputValue
(
n
.
Addr
.
Absolute
(
ctx
.
Path
()))
if
n
.
Output
!=
nil
{
*
n
.
Output
=
os
}
return
os
,
nil
}
// EvalPlanOutputChange is an EvalNode implementation that computes the
// required change (if any) for an output value.
type
EvalPlanOutputChange
struct
{
Addr
addrs
.
OutputValue
PriorState
**
states
.
OutputValue
Config
*
configs
.
Output
// Output, if non-nil, will have the planned change assigned to its
// referent.
Output
**
plans
.
OutputChange
// OutputState, if non-nil, will have written to it a synthetic "planned
// state" for the given output, which describes in as much detail as
// possible the new state we expect for this output after apply.
OutputState
**
states
.
OutputValue
}
// TODO: test
func
(
n
*
EvalPlanOutputChange
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
addr
:=
n
.
Addr
.
Absolute
(
ctx
.
Path
())
config
:=
n
.
Config
state
:=
*
n
.
PriorState
log
.
Printf
(
"[TRACE] EvalPlanOutputChange: %s"
,
addr
)
val
,
diags
:=
ctx
.
EvaluateExpr
(
config
.
Expr
,
cty
.
DynamicPseudoType
,
nil
)
if
diags
.
HasErrors
()
{
return
nil
,
diags
.
Err
()
}
change
:=
&
plans
.
OutputChange
{
Addr
:
addr
,
Sensitive
:
config
.
Sensitive
,
}
eqV
:=
val
.
Equals
(
state
.
Value
)
if
!
eqV
.
IsKnown
()
||
eqV
.
False
()
{
change
.
After
=
val
switch
{
case
state
==
nil
:
change
.
Action
=
plans
.
Create
change
.
Before
=
cty
.
NullVal
(
cty
.
DynamicPseudoType
)
default
:
change
.
Action
=
plans
.
Update
change
.
Before
=
state
.
Value
if
state
.
Sensitive
{
change
.
Sensitive
=
true
// change is sensitive if either Before or After are sensitive
}
}
}
else
{
change
.
Action
=
plans
.
NoOp
change
.
After
=
state
.
Value
}
log
.
Printf
(
"[TRACE] EvalPlanOutputChange: %s has change action %s"
,
addr
,
change
.
Action
)
if
n
.
Output
!=
nil
{
*
n
.
Output
=
change
}
if
n
.
OutputState
!=
nil
{
*
n
.
OutputState
=
&
states
.
OutputValue
{
Value
:
change
.
After
,
Sensitive
:
config
.
Sensitive
,
}
}
return
change
,
diags
.
ErrWithWarnings
()
}
// EvalPlanOutputDestroy is an EvalNode implementation that produces a
// destroy change for an output value. This should be used only if the
// output has been entirely removed from configuration. Setting an output
// to null should be handled by EvalPlanOutputChange.
type
EvalPlanOutputDestroy
struct
{
Addr
addrs
.
OutputValue
PriorState
**
states
.
OutputValue
// Output, if non-nil, will have the planned change assigned to its
// referent.
Output
**
plans
.
OutputChange
// OutputState, if non-nil, will have nil written to its referent, to
// represent that there will be no state for this output after the
// change is applied.
OutputState
**
states
.
OutputValue
}
// TODO: test
func
(
n
*
EvalPlanOutputDestroy
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
addr
:=
n
.
Addr
.
Absolute
(
ctx
.
Path
())
state
:=
*
n
.
PriorState
change
:=
&
plans
.
OutputChange
{
Addr
:
addr
,
Sensitive
:
state
.
Sensitive
,
Change
:
plans
.
Change
{
Action
:
plans
.
Delete
,
Before
:
state
.
Value
,
After
:
cty
.
NullVal
(
cty
.
DynamicPseudoType
),
},
}
log
.
Printf
(
"[TRACE] EvalPlanOutputDestroy: %s has change action %s"
,
addr
,
change
.
Action
)
if
n
.
Output
!=
nil
{
*
n
.
Output
=
change
}
if
n
.
OutputState
!=
nil
{
*
n
.
OutputState
=
nil
}
return
change
,
nil
}
// EvalWriteOutputChange is an EvalNode implementation that appends a given
// planned output change into the current changeset.
type
EvalWriteOutputChange
struct
{
Change
**
plans
.
OutputChange
}
// TODO: test
func
(
n
*
EvalWriteOutputChange
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
change
:=
*
n
.
Change
src
,
err
:=
change
.
Encode
()
if
err
!=
nil
{
// Should never happen, since our given change should always be valid
// having been built previously by EvalPlanOutputChange
return
nil
,
fmt
.
Errorf
(
"failed to encode %s change for plan: %s"
,
change
.
Addr
,
err
)
}
ctx
.
Changes
()
.
AppendOutputChange
(
src
)
return
nil
,
nil
}
// EvalWriteOutput is an EvalNode implementation that writes the output
// for the given name to the current state.
type
EvalWriteOutput
struct
{
Addr
addrs
.
OutputValue
Sensitive
bool
Expr
hcl
.
Expression
// ContinueOnErr allows interpolation to fail during Input
ContinueOnErr
bool
// EvalReadOutputChange is an EvalNode implementation that retrieves the
// planned change for the output of the given address from the current
// changeset.
type
EvalReadOutputChange
struct
{
Addr
addrs
.
OutputValue
Output
**
plans
.
OutputChange
}
// TODO: test
func
(
n
*
EvalWriteOutput
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
// This has to run before we have a state lock, since evaluation also
// reads the state
val
,
diags
:=
ctx
.
EvaluateExpr
(
n
.
Expr
,
cty
.
DynamicPseudoType
,
nil
)
// We'll handle errors below, after we have loaded the module.
func
(
n
*
EvalReadOutputChange
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
addr
:=
n
.
Addr
.
Absolute
(
ctx
.
Path
())
state
:=
ctx
.
State
()
if
state
==
nil
{
return
nil
,
nil
src
:=
ctx
.
Changes
()
.
GetOutputChange
(
addr
)
oc
,
err
:=
src
.
Decode
()
if
err
!=
nil
{
// Should happen only if someone's been tampering with the plan file
// manually, so we don't bother with a "pretty" error.
return
nil
,
fmt
.
Errorf
(
"failed to decode %s change from plan: %s"
,
addr
,
err
)
}
if
n
.
Output
!=
nil
{
*
n
.
Output
=
oc
}
return
oc
,
nil
}
// EvalOutput is an EvalNode implementation that produces an updated value
// for an output.
type
EvalOutput
struct
{
Addr
addrs
.
OutputValue
Config
*
configs
.
Output
// Output will be assigned the new state for the output value.
Output
**
states
.
OutputValue
}
// TODO: test
func
(
n
*
EvalOutput
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
addr
:=
n
.
Addr
.
Absolute
(
ctx
.
Path
())
config
:=
*
n
.
Config
// handling the interpolation error
val
,
diags
:=
ctx
.
EvaluateExpr
(
config
.
Expr
,
cty
.
DynamicPseudoType
,
nil
)
if
diags
.
HasErrors
()
{
if
n
.
ContinueOnErr
||
flagWarnOutputErrors
{
log
.
Printf
(
"[ERROR] Output interpolation %q failed: %s"
,
n
.
Addr
.
Name
,
diags
.
Err
())
// if we're continuing, make sure the output is included, and
// marked as unknown. If the evaluator was able to find a type
// for the value in spite of the error then we'll use it.
state
.
SetOutputValue
(
addr
,
cty
.
UnknownVal
(
val
.
Type
()),
n
.
Sensitive
)
return
nil
,
EvalEarlyExitError
{}
}
return
nil
,
diags
.
Err
()
}
if
val
.
IsKnown
()
&&
!
val
.
IsNull
()
{
state
.
SetOutputValue
(
addr
,
val
,
n
.
Sensitive
)
var
os
*
states
.
OutputValue
os
=
&
states
.
OutputValue
{
Value
:
val
,
Sensitive
:
config
.
Sensitive
,
}
if
os
.
Sensitive
{
log
.
Printf
(
"[TRACE] EvalOutput: %s is sensitive"
,
addr
)
}
else
{
state
.
RemoveOutputValue
(
addr
)
log
.
Printf
(
"[TRACE] EvalOutput: %s has new value %#v"
,
addr
,
os
.
Value
)
}
if
n
.
Output
!=
nil
{
*
n
.
Output
=
os
}
return
os
,
diags
.
ErrWithWarnings
()
}
// EvalWriteOutputState is an EvalNode implementation that updates the state
// for a given output.
type
EvalWriteOutputState
struct
{
Addr
addrs
.
OutputValue
State
**
states
.
OutputValue
}
// TODO: test
func
(
n
*
EvalWriteOutputState
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
addr
:=
n
.
Addr
.
Absolute
(
ctx
.
Path
())
os
:=
*
n
.
State
state
:=
ctx
.
State
()
if
state
==
nil
{
return
nil
,
nil
}
state
.
SetOutputValue
(
addr
,
os
.
Value
,
os
.
Sensitive
)
return
nil
,
nil
}
// EvalDeleteOutput is an EvalNode implementation that deletes an output
// from the state.
type
EvalDeleteOutput
struct
{
Addr
addrs
.
OutputValue
}
// TODO: test
func
(
n
*
EvalDeleteOutput
)
Eval
(
ctx
EvalContext
)
(
interface
{},
error
)
{
state
:=
ctx
.
State
()
if
state
==
nil
{
return
nil
,
nil
}
state
.
RemoveOutputValue
(
n
.
Addr
.
Absolute
(
ctx
.
Path
()))
return
nil
,
nil
}
This diff is collapsed.
Click to expand it.
terraform/graph_builder_apply.go
+
5
-
1
View file @
b4faa6c9
...
...
@@ -116,7 +116,11 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
&
LocalTransformer
{
Config
:
b
.
Config
},
// Add the outputs
&
OutputTransformer
{
Config
:
b
.
Config
},
&
OutputTransformer
{
Config
:
b
.
Config
,
State
:
b
.
State
,
NewNode
:
NewOutputApplyNode
,
},
// Add module variables
&
ModuleVariableTransformer
{
Config
:
b
.
Config
},
...
...
This diff is collapsed.
Click to expand it.
terraform/graph_builder_plan.go
+
6
-
8
View file @
b4faa6c9
...
...
@@ -79,8 +79,12 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
// Add the local values
&
LocalTransformer
{
Config
:
b
.
Config
},
// Add the outputs
&
OutputTransformer
{
Config
:
b
.
Config
},
// Add nodes for configured outputs and orphan outputs
&
OutputTransformer
{
Config
:
b
.
Config
,
State
:
b
.
State
,
NewNode
:
NewOutputPlanNode
,
},
// Add orphan resources
&
OrphanResourceTransformer
{
...
...
@@ -89,12 +93,6 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
Config
:
b
.
Config
,
},
// Create orphan output nodes
&
OrphanOutputTransformer
{
Config
:
b
.
Config
,
State
:
b
.
State
,
},
// Attach the configuration to any resources
&
AttachResourceConfigTransformer
{
Config
:
b
.
Config
},
...
...
This diff is collapsed.
Click to expand it.
terraform/node_output.go
+
158
-
13
View file @
b4faa6c9
...
...
@@ -3,14 +3,157 @@ package terraform
import
(
"fmt"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/lang"
)
// NodePlannableOutput represents an output that is "plannable":
// we want to check if the output value has changed and record that change
// in the plan if so.
//
// If the node has no attached configuration, the planned change will be to
// remove the output altogether.
type
NodePlannableOutput
struct
{
Addr
addrs
.
AbsOutputValue
Config
*
configs
.
Output
// Config is the output in the config
}
var
(
_
GraphNodeSubPath
=
(
*
NodePlannableOutput
)(
nil
)
_
RemovableIfNotTargeted
=
(
*
NodePlannableOutput
)(
nil
)
_
GraphNodeTargetDownstream
=
(
*
NodePlannableOutput
)(
nil
)
_
GraphNodeReferenceable
=
(
*
NodePlannableOutput
)(
nil
)
_
GraphNodeReferencer
=
(
*
NodePlannableOutput
)(
nil
)
_
GraphNodeReferenceOutside
=
(
*
NodePlannableOutput
)(
nil
)
_
GraphNodeEvalable
=
(
*
NodePlannableOutput
)(
nil
)
_
dag
.
GraphNodeDotter
=
(
*
NodePlannableOutput
)(
nil
)
)
// NewOutputPlanNode constructs a new graph node that will make a plan for
// an output with the given address and configuration.
//
// The configuration may be nil, in which case the node will plan to remove
// the output from the state altogether.
func
NewOutputPlanNode
(
addr
addrs
.
AbsOutputValue
,
cfg
*
configs
.
Output
)
dag
.
Vertex
{
return
&
NodePlannableOutput
{
Addr
:
addr
,
Config
:
cfg
,
}
}
func
(
n
*
NodePlannableOutput
)
Name
()
string
{
return
n
.
Addr
.
String
()
}
// GraphNodeSubPath
func
(
n
*
NodePlannableOutput
)
Path
()
addrs
.
ModuleInstance
{
return
n
.
Addr
.
Module
}
// RemovableIfNotTargeted
func
(
n
*
NodePlannableOutput
)
RemoveIfNotTargeted
()
bool
{
// We need to add this so that this node will be removed if
// it isn't targeted or a dependency of a target.
return
true
}
// GraphNodeTargetDownstream
func
(
n
*
NodePlannableOutput
)
TargetDownstream
(
targetedDeps
,
untargetedDeps
*
dag
.
Set
)
bool
{
// If any of the direct dependencies of an output are targeted then
// the output must always be targeted as well, so its value will always
// be up-to-date at the completion of an apply walk.
return
true
}
// GraphNodeReferenceOutside implementation
func
(
n
*
NodePlannableOutput
)
ReferenceOutside
()
(
selfPath
,
referencePath
addrs
.
ModuleInstance
)
{
return
referenceOutsideForOutput
(
n
.
Addr
)
}
// GraphNodeReferenceable
func
(
n
*
NodePlannableOutput
)
ReferenceableAddrs
()
[]
addrs
.
Referenceable
{
return
referenceableAddrsForOutput
(
n
.
Addr
)
}
// GraphNodeReferencer
func
(
n
*
NodePlannableOutput
)
References
()
[]
*
addrs
.
Reference
{
return
appendResourceDestroyReferences
(
referencesForOutput
(
n
.
Config
))
}
// GraphNodeEvalable
func
(
n
*
NodePlannableOutput
)
EvalTree
()
EvalNode
{
var
state
*
states
.
OutputValue
var
change
*
plans
.
OutputChange
return
&
EvalSequence
{
Nodes
:
[]
EvalNode
{
&
EvalReadOutputState
{
Addr
:
n
.
Addr
.
OutputValue
,
Output
:
&
state
,
},
&
EvalIf
{
If
:
func
(
EvalContext
)
(
bool
,
error
)
{
if
state
==
nil
&&
n
.
Config
==
nil
{
// Nothing to do, then. (Shouldn't have created a graph node at all.)
return
false
,
EvalEarlyExitError
{}
}
return
n
.
Config
!=
nil
,
nil
},
Then
:
&
EvalPlanOutputChange
{
Addr
:
n
.
Addr
.
OutputValue
,
Config
:
n
.
Config
,
PriorState
:
&
state
,
Output
:
&
change
,
},
Else
:
&
EvalPlanOutputDestroy
{
Addr
:
n
.
Addr
.
OutputValue
,
PriorState
:
&
state
,
Output
:
&
change
,
},
},
&
EvalWriteOutputChange
{
Change
:
&
change
,
},
},
}
}
// dag.GraphNodeDotter impl.
func
(
n
*
NodePlannableOutput
)
DotNode
(
name
string
,
opts
*
dag
.
DotOpts
)
*
dag
.
DotNode
{
return
&
dag
.
DotNode
{
Name
:
name
,
Attrs
:
map
[
string
]
string
{
"label"
:
n
.
Name
(),
"shape"
:
"note"
,
},
}
}
// NewOutputApplyNode constructs a new graph node that will update the state
// for an output with the given address and configuration.
//
// The configuration may be nil, in which case the node will remove the output
// from the state altogether.
func
NewOutputApplyNode
(
addr
addrs
.
AbsOutputValue
,
cfg
*
configs
.
Output
)
dag
.
Vertex
{
if
cfg
==
nil
{
return
&
NodeDestroyableOutput
{
Addr
:
addr
,
}
}
return
&
NodeApplyableOutput
{
Addr
:
addr
,
Config
:
cfg
,
}
}
// NodeApplyableOutput represents an output that is "applyable":
// it
is ready to be applied
.
// it
needs its state value updated to reflect its configuration
.
type
NodeApplyableOutput
struct
{
Addr
addrs
.
AbsOutputValue
Config
*
configs
.
Output
// Config is the output in the config
...
...
@@ -113,15 +256,18 @@ func (n *NodeApplyableOutput) References() []*addrs.Reference {
// GraphNodeEvalable
func
(
n
*
NodeApplyableOutput
)
EvalTree
()
EvalNode
{
var
state
*
states
.
OutputValue
return
&
EvalSequence
{
Nodes
:
[]
EvalNode
{
&
EvalOpFilter
{
Ops
:
[]
walkOperation
{
walkRefresh
,
walkPlan
,
walkApply
,
walkValidate
,
walkDestroy
,
walkPlanDestroy
},
Node
:
&
EvalWriteOutput
{
Addr
:
n
.
Addr
.
OutputValue
,
Sensitive
:
n
.
Config
.
Sensitive
,
Expr
:
n
.
Config
.
Expr
,
},
&
EvalOutput
{
Addr
:
n
.
Addr
.
OutputValue
,
Config
:
n
.
Config
,
Output
:
&
state
,
},
&
EvalWriteOutputState
{
Addr
:
n
.
Addr
.
OutputValue
,
State
:
&
state
,
},
},
}
...
...
@@ -138,11 +284,10 @@ func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNo
}
}
// NodeDestroyableOutput represents an output that is "destroy
b
ale":
//
its application
will remove the output from the state.
// NodeDestroyableOutput represents an output that is "destroya
b
le":
//
evaluating it
will remove the output from the state
altogether
.
type
NodeDestroyableOutput
struct
{
Addr
addrs
.
AbsOutputValue
Config
*
configs
.
Output
// Config is the output in the config
Addr
addrs
.
AbsOutputValue
}
var
(
...
...
@@ -178,7 +323,7 @@ func (n *NodeDestroyableOutput) TargetDownstream(targetedDeps, untargetedDeps *d
// GraphNodeReferencer
func
(
n
*
NodeDestroyableOutput
)
References
()
[]
*
addrs
.
Reference
{
return
referencesForOutput
(
n
.
Config
)
return
nil
}
// GraphNodeEvalable
...
...
This diff is collapsed.
Click to expand it.
terraform/transform_orphan_output.go
deleted
100644 → 0
+
0
-
60
View file @
10388beb
package
terraform
import
(
"log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/states"
)
// OrphanOutputTransformer finds the outputs that aren't present
// in the given config that are in the state and adds them to the graph
// for deletion.
type
OrphanOutputTransformer
struct
{
Config
*
configs
.
Config
// Root of config tree
State
*
states
.
State
// State is the root state
}
func
(
t
*
OrphanOutputTransformer
)
Transform
(
g
*
Graph
)
error
{
if
t
.
State
==
nil
{
log
.
Printf
(
"[DEBUG] No state, no orphan outputs"
)
return
nil
}
for
_
,
ms
:=
range
t
.
State
.
Modules
{
if
err
:=
t
.
transform
(
g
,
ms
);
err
!=
nil
{
return
err
}
}
return
nil
}
func
(
t
*
OrphanOutputTransformer
)
transform
(
g
*
Graph
,
ms
*
states
.
Module
)
error
{
if
ms
==
nil
{
return
nil
}
moduleAddr
:=
ms
.
Addr
// Get the config for this path, which is nil if the entire module has been
// removed.
var
outputs
map
[
string
]
*
configs
.
Output
if
c
:=
t
.
Config
.
DescendentForInstance
(
moduleAddr
);
c
!=
nil
{
outputs
=
c
.
Module
.
Outputs
}
// An output is "orphaned" if it's present in the state but not declared
// in the configuration.
for
name
:=
range
ms
.
OutputValues
{
if
_
,
exists
:=
outputs
[
name
];
exists
{
continue
}
g
.
Add
(
&
NodeOutputOrphan
{
Addr
:
addrs
.
OutputValue
{
Name
:
name
}
.
Absolute
(
moduleAddr
),
})
}
return
nil
}
This diff is collapsed.
Click to expand it.
terraform/transform_output.go
+
37
-
47
View file @
b4faa6c9
package
terraform
import
(
"log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/states"
)
// OutputTransformer is a GraphTransformer that adds all the outputs
// in the configuration to the graph.
// OutputTransformer is a GraphTransformer that adds nodes for all outputs
// that exist in the configuration or the state, allowing us to evaluate new
// values for them or remove them altogether.
//
// This is done for the apply graph builder even if dependent nodes
// aren't changing since there is no downside: the state will be available
// even if the dependent items aren't changing.
// The caller must provide a factory functions for constructing both evaluate
// nodes and destroy nodes, allowing different concrete node types to be used
// during different graph walks. For destroy nodes, the given configuration
// is nil.
type
OutputTransformer
struct
{
Config
*
configs
.
Config
State
*
states
.
State
NewNode
func
(
addr
addrs
.
AbsOutputValue
,
config
*
configs
.
Output
)
dag
.
Vertex
}
func
(
t
*
OutputTransformer
)
Transform
(
g
*
Graph
)
error
{
return
t
.
transform
(
g
,
t
.
Config
)
err
:=
t
.
transformConfig
(
g
,
t
.
Config
)
if
err
!=
nil
{
return
err
}
return
t
.
transformOrphans
(
g
)
}
func
(
t
*
OutputTransformer
)
transform
(
g
*
Graph
,
c
*
configs
.
Config
)
error
{
func
(
t
*
OutputTransformer
)
transform
Config
(
g
*
Graph
,
c
*
configs
.
Config
)
error
{
// If we have no config then there can be no outputs.
if
c
==
nil
{
return
nil
...
...
@@ -31,7 +41,7 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error {
// we can reference module outputs and they must show up in the
// reference map.
for
_
,
cc
:=
range
c
.
Children
{
if
err
:=
t
.
transform
(
g
,
cc
);
err
!=
nil
{
if
err
:=
t
.
transform
Config
(
g
,
cc
);
err
!=
nil
{
return
err
}
}
...
...
@@ -46,50 +56,30 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error {
for
_
,
o
:=
range
c
.
Module
.
Outputs
{
addr
:=
path
.
OutputValue
(
o
.
Name
)
node
:=
&
NodeApplyableOutput
{
Addr
:
addr
,
Config
:
o
,
}
node
:=
t
.
NewNode
(
addr
,
o
)
g
.
Add
(
node
)
}
return
nil
}
// DestroyOutputTransformer is a GraphTransformer that adds nodes to delete
// outputs during destroy. We need to do this to ensure that no stale outputs
// are ever left in the state.
type
DestroyOutputTransformer
struct
{
}
func
(
t
*
DestroyOutputTransformer
)
Transform
(
g
*
Graph
)
error
{
for
_
,
v
:=
range
g
.
Vertices
()
{
output
,
ok
:=
v
.
(
*
NodeApplyableOutput
)
if
!
ok
{
continue
}
// create the destroy node for this output
node
:=
&
NodeDestroyableOutput
{
Addr
:
output
.
Addr
,
Config
:
output
.
Config
,
}
log
.
Printf
(
"[TRACE] creating %s"
,
node
.
Name
())
g
.
Add
(
node
)
deps
,
err
:=
g
.
Descendents
(
v
)
if
err
!=
nil
{
return
err
}
// the destroy node must depend on the eval node
deps
.
Add
(
v
)
for
_
,
d
:=
range
deps
.
List
()
{
log
.
Printf
(
"[TRACE] %s depends on %s"
,
node
.
Name
(),
dag
.
VertexName
(
d
))
g
.
Connect
(
dag
.
BasicEdge
(
node
,
d
))
func
(
t
*
OutputTransformer
)
transformOrphans
(
g
*
Graph
)
error
{
// "orphans" here are any outputs present in the state that are not
// present in the configuration, which we'll therefore need to remove from
// the state after apply.
for
_
,
ms
:=
range
t
.
State
.
Modules
{
cfg
:=
t
.
Config
.
DescendentForInstance
(
ms
.
Addr
)
for
name
:=
range
ms
.
OutputValues
{
addr
:=
addrs
.
OutputValue
{
Name
:
name
}
.
Absolute
(
ms
.
Addr
)
n
:=
t
.
NewNode
(
addr
,
nil
)
if
cfg
==
nil
{
g
.
Add
(
n
)
}
else
if
_
,
exists
:=
cfg
.
Module
.
Outputs
[
name
];
!
exists
{
g
.
Add
(
n
)
}
}
}
return
nil
}
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment