terraform: minimal applies work!

This commit is contained in:
Mitchell Hashimoto 2016-09-13 17:52:09 -07:00
parent dc9b9eee44
commit 5828a0a9ac
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 233 additions and 1 deletions

View File

@ -362,7 +362,9 @@ func (c *Context) Apply() (*State, error) {
graph, err = c.Graph(&ContextGraphOpts{Validate: true})
} else {
graph, err = (&ApplyGraphBuilder{
Config: c.module,
Diff: c.diff,
State: c.state,
Providers: c.providersList(),
Provisioners: c.provisionersList(),
}).Build(RootModulePath)

View File

@ -18,6 +18,9 @@ type ApplyGraphBuilder struct {
// Diff is the diff to apply.
Diff *Diff
// State is the current state
State *State
// Providers is the list of providers supported.
Providers []string
@ -37,7 +40,11 @@ func (b *ApplyGraphBuilder) Build(path []string) (*Graph, error) {
func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
steps := []GraphTransformer{
// Creates all the nodes represented in the diff.
&DiffTransformer{Diff: b.Diff},
&DiffTransformer{
Diff: b.Diff,
Config: b.Config,
State: b.State,
},
// Create all the providers
&MissingProviderTransformer{Providers: b.Providers},

View File

@ -1,6 +1,8 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
)
@ -31,3 +33,206 @@ func (n *NodeApplyableResource) ProvidedBy() []string {
// Use our type
return []string{resourceProvider(n.Addr.Type, "")}
}
// GraphNodeEvalable
func (n *NodeApplyableResource) EvalTree() EvalNode {
// stateId is the ID to put into the state
stateId := n.Addr.stateId()
if n.Addr.Index > -1 {
stateId = fmt.Sprintf("%s.%d", stateId, n.Addr.Index)
}
// Build the instance info. More of this will be populated during eval
info := &InstanceInfo{
Id: stateId,
Type: n.Addr.Type,
}
// Build the resource for eval
resource := &Resource{
Name: n.Addr.Name,
Type: n.Addr.Type,
CountIndex: n.Addr.Index,
}
if resource.CountIndex < 0 {
resource.CountIndex = 0
}
// 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}
stateDeps = oldN.StateDependencies()
}
// 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 diff *InstanceDiff
var state *InstanceState
var resourceConfig *ResourceConfig
var err error
var createNew bool
var createBeforeDestroyEnabled bool
return &EvalSequence{
Nodes: []EvalNode{
// Build the instance info
&EvalInstanceInfo{
Info: info,
},
// Get the saved diff for apply
&EvalReadDiff{
Name: stateId,
Diff: &diff,
},
// We don't want to do any destroys
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if diff == nil {
return true, EvalEarlyExitError{}
}
if diff.GetDestroy() && diff.GetAttributesLen() == 0 {
return true, EvalEarlyExitError{}
}
diff.SetDestroy(false)
return true, nil
},
Then: EvalNoop{},
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
destroy := false
if diff != nil {
destroy = diff.GetDestroy() || diff.RequiresNew()
}
createBeforeDestroyEnabled =
n.Config.Lifecycle.CreateBeforeDestroy &&
destroy
return createBeforeDestroyEnabled, nil
},
Then: &EvalDeposeState{
Name: stateId,
},
},
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(),
Resource: resource,
Output: &resourceConfig,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: stateId,
Output: &state,
},
// Re-run validation to catch any errors we missed, e.g. type
// mismatches on computed values.
&EvalValidateResource{
Provider: &provider,
Config: &resourceConfig,
ResourceName: n.Config.Name,
ResourceType: n.Config.Type,
ResourceMode: n.Config.Mode,
IgnoreWarnings: true,
},
&EvalDiff{
Info: info,
Config: &resourceConfig,
Resource: n.Config,
Provider: &provider,
Diff: &diff,
State: &state,
OutputDiff: &diff,
},
// Get the saved diff
&EvalReadDiff{
Name: stateId,
Diff: &diff,
},
// Compare the diffs
&EvalCompareDiff{
Info: info,
One: &diff,
Two: &diff,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: stateId,
Output: &state,
},
&EvalApply{
Info: info,
State: &state,
Diff: &diff,
Provider: &provider,
Output: &state,
Error: &err,
CreateNew: &createNew,
},
&EvalWriteState{
Name: stateId,
ResourceType: n.Config.Type,
Provider: n.Config.Provider,
Dependencies: stateDeps,
State: &state,
},
&EvalApplyProvisioners{
Info: info,
State: &state,
Resource: n.Config,
InterpResource: resource,
CreateNew: &createNew,
Error: &err,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return createBeforeDestroyEnabled && err != nil, nil
},
Then: &EvalUndeposeState{
Name: stateId,
State: &state,
},
Else: &EvalWriteState{
Name: stateId,
ResourceType: n.Config.Type,
Provider: n.Config.Provider,
Dependencies: stateDeps,
State: &state,
},
},
// We clear the diff out here so that future nodes
// don't see a diff that is already complete. There
// is no longer a diff!
&EvalWriteDiff{
Name: stateId,
Diff: nil,
},
&EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
&EvalUpdateStateHook{},
},
}
}

View File

@ -85,6 +85,24 @@ func (r *ResourceAddress) String() string {
return strings.Join(result, ".")
}
// stateId returns the ID that this resource should be entered with
// in the state. This is also used for diffs. In the future, we'd like to
// move away from this string field so I don't export this.
// TODO: test
func (r *ResourceAddress) stateId() string {
result := fmt.Sprintf("%s.%s", r.Type, r.Name)
switch r.Mode {
case config.ManagedResourceMode:
// Done
case config.DataResourceMode:
result = fmt.Sprintf("data.%s", result)
default:
panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
}
return result
}
// parseResourceAddressConfig creates a resource address from a config.Resource
func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
return &ResourceAddress{