terraform: starting up the plans

This commit is contained in:
Mitchell Hashimoto 2015-02-11 15:22:03 -08:00
parent a78fe784b8
commit aae2d4c780
9 changed files with 1595 additions and 1354 deletions

View File

@ -26,6 +26,7 @@ type ContextOpts struct {
// perform operations on infrastructure. This structure is built using // perform operations on infrastructure. This structure is built using
// NewContext. See the documentation for that. // NewContext. See the documentation for that.
type Context2 struct { type Context2 struct {
diff *Diff
hooks []Hook hooks []Hook
module *module.Tree module *module.Tree
providers map[string]ResourceProviderFactory providers map[string]ResourceProviderFactory
@ -41,12 +42,19 @@ type Context2 struct {
// should not be mutated in any way, since the pointers are copied, not // should not be mutated in any way, since the pointers are copied, not
// the values themselves. // the values themselves.
func NewContext2(opts *ContextOpts) *Context2 { func NewContext2(opts *ContextOpts) *Context2 {
state := opts.State
if state == nil {
state = new(State)
state.init()
}
return &Context2{ return &Context2{
diff: opts.Diff,
hooks: opts.Hooks, hooks: opts.Hooks,
module: opts.Module, module: opts.Module,
providers: opts.Providers, providers: opts.Providers,
provisioners: opts.Provisioners, provisioners: opts.Provisioners,
state: opts.State, state: state,
variables: opts.Variables, variables: opts.Variables,
} }
} }
@ -73,6 +81,51 @@ func (c *Context2) GraphBuilder() GraphBuilder {
} }
} }
// Plan generates an execution plan for the given context.
//
// The execution plan encapsulates the context and can be stored
// in order to reinstantiate a context later for Apply.
//
// Plan also updates the diff of this context to be the diff generated
// by the plan, so Apply can be called after.
func (c *Context2) Plan(opts *PlanOpts) (*Plan, error) {
p := &Plan{
Module: c.module,
Vars: c.variables,
State: c.state,
}
var operation walkOperation
if opts != nil && opts.Destroy {
operation = walkPlanDestroy
} else {
// Set our state to be something temporary. We do this so that
// the plan can update a fake state so that variables work, then
// we replace it back with our old state.
old := c.state
if old == nil {
c.state = &State{}
c.state.init()
} else {
c.state = old.deepcopy()
}
defer func() {
c.state = old
}()
operation = walkPlan
}
// Do the walk
walker, err := c.walk(operation)
p.Diff = walker.Diff
// Update the diff so that our context is up-to-date
c.diff = p.Diff
return p, err
}
// Refresh goes through all the resources in the state and refreshes them // Refresh goes through all the resources in the state and refreshes them
// to their latest state. This will update the state that this context // to their latest state. This will update the state that this context
// works with, along with returning it. // works with, along with returning it.

File diff suppressed because it is too large Load Diff

View File

@ -49,6 +49,10 @@ type EvalContext interface {
// that is currently being acted upon. // that is currently being acted upon.
Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error) Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error)
// Diff returns the global diff as well as the lock that should
// be used to modify that diff.
Diff() (*Diff, *sync.RWMutex)
// State returns the global state as well as the lock that should // State returns the global state as well as the lock that should
// be used to modify that state. // be used to modify that state.
State() (*State, *sync.RWMutex) State() (*State, *sync.RWMutex)
@ -96,6 +100,10 @@ type MockEvalContext struct {
PathCalled bool PathCalled bool
PathPath []string PathPath []string
DiffCalled bool
DiffDiff *Diff
DiffLock *sync.RWMutex
StateCalled bool StateCalled bool
StateState *State StateState *State
StateLock *sync.RWMutex StateLock *sync.RWMutex
@ -156,6 +164,11 @@ func (c *MockEvalContext) Path() []string {
return c.PathPath return c.PathPath
} }
func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) {
c.DiffCalled = true
return c.DiffDiff, c.DiffLock
}
func (c *MockEvalContext) State() (*State, *sync.RWMutex) { func (c *MockEvalContext) State() (*State, *sync.RWMutex) {
c.StateCalled = true c.StateCalled = true
return c.StateState, c.StateLock return c.StateState, c.StateLock

View File

@ -22,6 +22,8 @@ type BuiltinEvalContext struct {
Provisioners map[string]ResourceProvisionerFactory Provisioners map[string]ResourceProvisionerFactory
ProvisionerCache map[string]ResourceProvisioner ProvisionerCache map[string]ResourceProvisioner
ProvisionerLock *sync.Mutex ProvisionerLock *sync.Mutex
DiffValue *Diff
DiffLock *sync.RWMutex
StateValue *State StateValue *State
StateLock *sync.RWMutex StateLock *sync.RWMutex
@ -177,6 +179,10 @@ func (ctx *BuiltinEvalContext) Path() []string {
return ctx.PathValue return ctx.PathValue
} }
func (ctx *BuiltinEvalContext) Diff() (*Diff, *sync.RWMutex) {
return ctx.DiffValue, ctx.DiffLock
}
func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) { func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) {
return ctx.StateValue, ctx.StateLock return ctx.StateValue, ctx.StateLock
} }

131
terraform/eval_diff.go Normal file
View File

@ -0,0 +1,131 @@
package terraform
// EvalDiff is an EvalNode implementation that does a refresh for
// a resource.
type EvalDiff struct {
Info *InstanceInfo
Config EvalNode
Provider EvalNode
State EvalNode
Output *InstanceDiff
}
func (n *EvalDiff) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.Config, n.Provider, n.State},
[]EvalType{EvalTypeConfig, EvalTypeResourceProvider,
EvalTypeInstanceState}
}
// TODO: test
func (n *EvalDiff) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
// Extract our arguments
var state *InstanceState
config := args[0].(*ResourceConfig)
provider := args[1].(ResourceProvider)
if args[2] != nil {
state = args[2].(*InstanceState)
}
// Call pre-diff hook
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreDiff(n.Info, state)
})
if err != nil {
return nil, err
}
// The state for the diff must never be nil
diffState := state
if diffState == nil {
diffState = new(InstanceState)
}
diffState.init()
// Diff!
diff, err := provider.Diff(n.Info, diffState, config)
if err != nil {
return nil, err
}
if diff == nil {
diff = new(InstanceDiff)
}
// Require a destroy if there is no ID and it requires new.
if diff.RequiresNew() && state != nil && state.ID != "" {
diff.Destroy = true
}
// If we're creating a new resource, compute its ID
if diff.RequiresNew() || state == nil || state.ID == "" {
var oldID string
if state != nil {
oldID = state.Attributes["id"]
}
// Add diff to compute new ID
diff.init()
diff.Attributes["id"] = &ResourceAttrDiff{
Old: oldID,
NewComputed: true,
RequiresNew: true,
Type: DiffAttrOutput,
}
}
// Call post-refresh hook
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostDiff(n.Info, diff)
})
if err != nil {
return nil, err
}
// Update our output
*n.Output = *diff
// Merge our state so that the state is updated with our plan
if !diff.Empty() {
state = state.MergeDiff(diff)
}
return state, nil
}
func (n *EvalDiff) Type() EvalType {
return EvalTypeInstanceState
}
// EvalWriteDiff is an EvalNode implementation that writes the diff to
// the full diff.
type EvalWriteDiff struct {
Name string
Diff *InstanceDiff
}
func (n *EvalWriteDiff) Args() ([]EvalNode, []EvalType) {
return nil, nil
}
// TODO: test
func (n *EvalWriteDiff) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
diff, lock := ctx.Diff()
// Acquire the lock so that we can do this safely concurrently
lock.Lock()
defer lock.Unlock()
// Write the diff
modDiff := diff.ModuleByPath(ctx.Path())
if modDiff == nil {
modDiff = diff.AddModule(ctx.Path())
}
modDiff.Resources[n.Name] = n.Diff
return nil, nil
}
func (n *EvalWriteDiff) Type() EvalType {
return EvalTypeNull
}

View File

@ -17,5 +17,6 @@ const (
EvalTypeConfig // *ResourceConfig EvalTypeConfig // *ResourceConfig
EvalTypeResourceProvider // ResourceProvider EvalTypeResourceProvider // ResourceProvider
EvalTypeResourceProvisioner // ResourceProvisioner EvalTypeResourceProvisioner // ResourceProvisioner
EvalTypeInstanceDiff // *InstanceDiff
EvalTypeInstanceState // *InstanceState EvalTypeInstanceState // *InstanceState
) )

View File

@ -10,7 +10,8 @@ const (
_EvalType_name_2 = "EvalTypeConfig" _EvalType_name_2 = "EvalTypeConfig"
_EvalType_name_3 = "EvalTypeResourceProvider" _EvalType_name_3 = "EvalTypeResourceProvider"
_EvalType_name_4 = "EvalTypeResourceProvisioner" _EvalType_name_4 = "EvalTypeResourceProvisioner"
_EvalType_name_5 = "EvalTypeInstanceState" _EvalType_name_5 = "EvalTypeInstanceDiff"
_EvalType_name_6 = "EvalTypeInstanceState"
) )
var ( var (
@ -19,7 +20,8 @@ var (
_EvalType_index_2 = [...]uint8{0, 14} _EvalType_index_2 = [...]uint8{0, 14}
_EvalType_index_3 = [...]uint8{0, 24} _EvalType_index_3 = [...]uint8{0, 24}
_EvalType_index_4 = [...]uint8{0, 27} _EvalType_index_4 = [...]uint8{0, 27}
_EvalType_index_5 = [...]uint8{0, 21} _EvalType_index_5 = [...]uint8{0, 20}
_EvalType_index_6 = [...]uint8{0, 21}
) )
func (i EvalType) String() string { func (i EvalType) String() string {
@ -36,6 +38,8 @@ func (i EvalType) String() string {
return _EvalType_name_4 return _EvalType_name_4
case i == 32: case i == 32:
return _EvalType_name_5 return _EvalType_name_5
case i == 64:
return _EvalType_name_6
default: default:
return fmt.Sprintf("EvalType(%d)", i) return fmt.Sprintf("EvalType(%d)", i)
} }

View File

@ -19,11 +19,13 @@ type ContextGraphWalker struct {
// Outputs, do not set these. Do not read these while the graph // Outputs, do not set these. Do not read these while the graph
// is being walked. // is being walked.
EvalError error EvalError error
Diff *Diff
ValidationWarnings []string ValidationWarnings []string
ValidationErrors []error ValidationErrors []error
errorLock sync.Mutex errorLock sync.Mutex
once sync.Once once sync.Once
diffLock sync.RWMutex
providerCache map[string]ResourceProvider providerCache map[string]ResourceProvider
providerConfigCache map[string]*ResourceConfig providerConfigCache map[string]*ResourceConfig
providerLock sync.Mutex providerLock sync.Mutex
@ -44,6 +46,8 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
Provisioners: w.Context.provisioners, Provisioners: w.Context.provisioners,
ProvisionerCache: w.provisionerCache, ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock, ProvisionerLock: &w.provisionerLock,
DiffValue: w.Diff,
DiffLock: &w.diffLock,
StateValue: w.Context.state, StateValue: w.Context.state,
StateLock: &w.Context.stateLock, StateLock: &w.Context.stateLock,
Interpolater: &Interpolater{ Interpolater: &Interpolater{
@ -87,6 +91,9 @@ func (w *ContextGraphWalker) ExitEvalTree(
} }
func (w *ContextGraphWalker) init() { func (w *ContextGraphWalker) init() {
w.Diff = new(Diff)
w.Diff.init()
w.providerCache = make(map[string]ResourceProvider, 5) w.providerCache = make(map[string]ResourceProvider, 5)
w.providerConfigCache = make(map[string]*ResourceConfig, 5) w.providerConfigCache = make(map[string]*ResourceConfig, 5)
w.provisionerCache = make(map[string]ResourceProvisioner, 5) w.provisionerCache = make(map[string]ResourceProvisioner, 5)

View File

@ -115,6 +115,32 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
}, },
}) })
// Diff the resource
var diff InstanceDiff
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &EvalDiff{
Info: info,
Config: &EvalInterpolate{Config: n.Resource.RawConfig},
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
State: &EvalReadState{Name: n.stateId()},
Output: &diff,
},
},
&EvalWriteDiff{
Name: n.stateId(),
Diff: &diff,
},
},
},
})
return seq return seq
} }