terraform: early exit and cancellation
This commit is contained in:
parent
d0c77d268a
commit
10e82375f2
|
@ -32,9 +32,13 @@ type Context2 struct {
|
||||||
module *module.Tree
|
module *module.Tree
|
||||||
providers map[string]ResourceProviderFactory
|
providers map[string]ResourceProviderFactory
|
||||||
provisioners map[string]ResourceProvisionerFactory
|
provisioners map[string]ResourceProvisionerFactory
|
||||||
|
sh *stopHook
|
||||||
state *State
|
state *State
|
||||||
stateLock sync.RWMutex
|
stateLock sync.RWMutex
|
||||||
variables map[string]string
|
variables map[string]string
|
||||||
|
|
||||||
|
l sync.Mutex // Lock acquired during any task
|
||||||
|
runCh <-chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext creates a new Context structure.
|
// NewContext creates a new Context structure.
|
||||||
|
@ -43,6 +47,13 @@ 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 {
|
||||||
|
// Copy all the hooks and add our stop hook. We don't append directly
|
||||||
|
// to the Config so that we're not modifying that in-place.
|
||||||
|
sh := new(stopHook)
|
||||||
|
hooks := make([]Hook, len(opts.Hooks)+1)
|
||||||
|
copy(hooks, opts.Hooks)
|
||||||
|
hooks[len(opts.Hooks)] = sh
|
||||||
|
|
||||||
state := opts.State
|
state := opts.State
|
||||||
if state == nil {
|
if state == nil {
|
||||||
state = new(State)
|
state = new(State)
|
||||||
|
@ -51,10 +62,11 @@ func NewContext2(opts *ContextOpts) *Context2 {
|
||||||
|
|
||||||
return &Context2{
|
return &Context2{
|
||||||
diff: opts.Diff,
|
diff: opts.Diff,
|
||||||
hooks: opts.Hooks,
|
hooks: hooks,
|
||||||
module: opts.Module,
|
module: opts.Module,
|
||||||
providers: opts.Providers,
|
providers: opts.Providers,
|
||||||
provisioners: opts.Provisioners,
|
provisioners: opts.Provisioners,
|
||||||
|
sh: sh,
|
||||||
state: state,
|
state: state,
|
||||||
variables: opts.Variables,
|
variables: opts.Variables,
|
||||||
}
|
}
|
||||||
|
@ -88,6 +100,9 @@ func (c *Context2) GraphBuilder() GraphBuilder {
|
||||||
// In addition to returning the resulting state, this context is updated
|
// In addition to returning the resulting state, this context is updated
|
||||||
// with the latest state.
|
// with the latest state.
|
||||||
func (c *Context2) Apply() (*State, error) {
|
func (c *Context2) Apply() (*State, error) {
|
||||||
|
v := c.acquireRun()
|
||||||
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.deepcopy()
|
||||||
|
|
||||||
|
@ -108,6 +123,9 @@ func (c *Context2) Apply() (*State, error) {
|
||||||
// Plan also updates the diff of this context to be the diff generated
|
// Plan also updates the diff of this context to be the diff generated
|
||||||
// by the plan, so Apply can be called after.
|
// by the plan, so Apply can be called after.
|
||||||
func (c *Context2) Plan(opts *PlanOpts) (*Plan, error) {
|
func (c *Context2) Plan(opts *PlanOpts) (*Plan, error) {
|
||||||
|
v := c.acquireRun()
|
||||||
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
p := &Plan{
|
p := &Plan{
|
||||||
Module: c.module,
|
Module: c.module,
|
||||||
Vars: c.variables,
|
Vars: c.variables,
|
||||||
|
@ -157,6 +175,9 @@ func (c *Context2) Plan(opts *PlanOpts) (*Plan, error) {
|
||||||
// Even in the case an error is returned, the state will be returned and
|
// Even in the case an error is returned, the state will be returned and
|
||||||
// will potentially be partially updated.
|
// will potentially be partially updated.
|
||||||
func (c *Context2) Refresh() (*State, []error) {
|
func (c *Context2) Refresh() (*State, []error) {
|
||||||
|
v := c.acquireRun()
|
||||||
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.deepcopy()
|
||||||
|
|
||||||
|
@ -172,8 +193,32 @@ func (c *Context2) Refresh() (*State, []error) {
|
||||||
return c.state, nil
|
return c.state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop stops the running task.
|
||||||
|
//
|
||||||
|
// Stop will block until the task completes.
|
||||||
|
func (c *Context2) Stop() {
|
||||||
|
c.l.Lock()
|
||||||
|
ch := c.runCh
|
||||||
|
|
||||||
|
// If we aren't running, then just return
|
||||||
|
if ch == nil {
|
||||||
|
c.l.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the hook we want to stop
|
||||||
|
c.sh.Stop()
|
||||||
|
|
||||||
|
// Wait for us to stop
|
||||||
|
c.l.Unlock()
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
|
||||||
// Validate validates the configuration and returns any warnings or errors.
|
// Validate validates the configuration and returns any warnings or errors.
|
||||||
func (c *Context2) Validate() ([]string, []error) {
|
func (c *Context2) Validate() ([]string, []error) {
|
||||||
|
v := c.acquireRun()
|
||||||
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
// Validate the configuration itself
|
// Validate the configuration itself
|
||||||
|
@ -201,6 +246,32 @@ func (c *Context2) Validate() ([]string, []error) {
|
||||||
return walker.ValidationWarnings, rerrs.Errors
|
return walker.ValidationWarnings, rerrs.Errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context2) acquireRun() chan<- struct{} {
|
||||||
|
c.l.Lock()
|
||||||
|
defer c.l.Unlock()
|
||||||
|
|
||||||
|
// Wait for no channel to exist
|
||||||
|
for c.runCh != nil {
|
||||||
|
c.l.Unlock()
|
||||||
|
ch := c.runCh
|
||||||
|
<-ch
|
||||||
|
c.l.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
c.runCh = ch
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context2) releaseRun(ch chan<- struct{}) {
|
||||||
|
c.l.Lock()
|
||||||
|
defer c.l.Unlock()
|
||||||
|
|
||||||
|
close(ch)
|
||||||
|
c.runCh = nil
|
||||||
|
c.sh.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context2) walk(operation walkOperation) (*ContextGraphWalker, error) {
|
func (c *Context2) walk(operation walkOperation) (*ContextGraphWalker, error) {
|
||||||
// Build the graph
|
// Build the graph
|
||||||
graph, err := c.GraphBuilder().Build(RootModulePath)
|
graph, err := c.GraphBuilder().Build(RootModulePath)
|
||||||
|
|
|
@ -2846,13 +2846,12 @@ func TestContext2Apply_badDiff(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func TestContext2Apply_cancel(t *testing.T) {
|
||||||
func TestContextApply_cancel(t *testing.T) {
|
|
||||||
stopped := false
|
stopped := false
|
||||||
|
|
||||||
m := testModule(t, "apply-cancel")
|
m := testModule(t, "apply-cancel")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
ctx := testContext(t, &ContextOpts{
|
ctx := testContext2(t, &ContextOpts{
|
||||||
Module: m,
|
Module: m,
|
||||||
Providers: map[string]ResourceProviderFactory{
|
Providers: map[string]ResourceProviderFactory{
|
||||||
"aws": testProviderFuncFixed(p),
|
"aws": testProviderFuncFixed(p),
|
||||||
|
@ -2907,7 +2906,7 @@ func TestContextApply_cancel(t *testing.T) {
|
||||||
|
|
||||||
mod := state.RootModule()
|
mod := state.RootModule()
|
||||||
if len(mod.Resources) != 1 {
|
if len(mod.Resources) != 1 {
|
||||||
t.Fatalf("bad: %#v", mod.Resources)
|
t.Fatalf("bad: %s", state.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := strings.TrimSpace(state.String())
|
actual := strings.TrimSpace(state.String())
|
||||||
|
@ -2916,7 +2915,6 @@ func TestContextApply_cancel(t *testing.T) {
|
||||||
t.Fatalf("bad: \n%s", actual)
|
t.Fatalf("bad: \n%s", actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
func TestContext2Apply_compute(t *testing.T) {
|
func TestContext2Apply_compute(t *testing.T) {
|
||||||
m := testModule(t, "apply-compute")
|
m := testModule(t, "apply-compute")
|
||||||
|
|
|
@ -36,10 +36,23 @@ func (EvalEarlyExitError) Error() string { return "early exit" }
|
||||||
// Eval evaluates the given EvalNode with the given context, properly
|
// Eval evaluates the given EvalNode with the given context, properly
|
||||||
// evaluating all args in the correct order.
|
// evaluating all args in the correct order.
|
||||||
func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {
|
func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {
|
||||||
|
// Call the lower level eval which doesn't understand early exit,
|
||||||
|
// and if we early exit, it isn't an error.
|
||||||
|
result, err := eval(n, ctx)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(EvalEarlyExitError); ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func eval(n EvalNode, ctx EvalContext) (interface{}, error) {
|
||||||
argNodes, _ := n.Args()
|
argNodes, _ := n.Args()
|
||||||
args := make([]interface{}, len(argNodes))
|
args := make([]interface{}, len(argNodes))
|
||||||
for i, n := range argNodes {
|
for i, n := range argNodes {
|
||||||
v, err := Eval(n, ctx)
|
v, err := eval(n, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (n *EvalApply) Eval(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
{
|
||||||
// Call pre-apply hook
|
// Call pre-apply hook
|
||||||
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
||||||
return h.PreApply(n.Info, state, diff)
|
return h.PreApply(n.Info, state, diff)
|
||||||
|
@ -51,7 +51,7 @@ func (n *EvalApply) Eval(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
*/
|
}
|
||||||
|
|
||||||
// With the completed diff, apply!
|
// With the completed diff, apply!
|
||||||
log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
|
log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
|
@ -40,6 +41,7 @@ func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
|
||||||
continue
|
continue
|
||||||
case HookActionHalt:
|
case HookActionHalt:
|
||||||
// Return an early exit error to trigger an early exit
|
// Return an early exit error to trigger an early exit
|
||||||
|
log.Printf("[WARN] Early exit triggered by hook: %T", h)
|
||||||
return EvalEarlyExitError{}
|
return EvalEarlyExitError{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue