Merge pull request #16833 from hashicorp/jbardin/plan-shutdown

Fully enable shutdown for plan and refresh in the local backend
This commit is contained in:
James Bardin 2017-12-05 16:48:34 -05:00 committed by GitHub
commit 12b7dac124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 76 additions and 57 deletions

View File

@ -151,14 +151,6 @@ func (b *Local) opApply(
_, applyErr = tfCtx.Apply() _, applyErr = tfCtx.Apply()
// we always want the state, even if apply failed // we always want the state, even if apply failed
applyState = tfCtx.State() applyState = tfCtx.State()
/*
// Record any shadow errors for later
if err := ctx.ShadowError(); err != nil {
shadowErr = multierror.Append(shadowErr, multierror.Prefix(
err, "apply operation:"))
}
*/
}() }()
// Wait for the apply to finish or for us to be interrupted so // Wait for the apply to finish or for us to be interrupted so

View File

@ -101,14 +101,34 @@ func (b *Local) opPlan(
} }
} }
// Perform the plan // Perform the plan in a goroutine so we can be interrupted
var plan *terraform.Plan
var planErr error
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
log.Printf("[INFO] backend/local: plan calling Plan") log.Printf("[INFO] backend/local: plan calling Plan")
plan, err := tfCtx.Plan() plan, planErr = tfCtx.Plan()
if err != nil { }()
runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
return select {
case <-ctx.Done():
if b.CLI != nil {
b.CLI.Output("stopping plan operation...")
} }
// Stop execution
go tfCtx.Stop()
// Wait for completion still
<-doneCh
case <-doneCh:
}
if planErr != nil {
runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", planErr)
return
}
// Record state // Record state
runningOp.PlanEmpty = plan.Diff.Empty() runningOp.PlanEmpty = plan.Diff.Empty()

View File

@ -3,6 +3,7 @@ package local
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
@ -12,6 +13,7 @@ import (
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
) )
func (b *Local) opRefresh( func (b *Local) opRefresh(
@ -78,11 +80,34 @@ func (b *Local) opRefresh(
} }
} }
// Perform operation and write the resulting state to the running op // Perform the refresh in a goroutine so we can be interrupted
newState, err := tfCtx.Refresh() var newState *terraform.State
var refreshErr error
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
newState, err = tfCtx.Refresh()
log.Printf("[INFO] backend/local: plan calling Plan")
}()
select {
case <-ctx.Done():
if b.CLI != nil {
b.CLI.Output("stopping refresh operation...")
}
// Stop execution
go tfCtx.Stop()
// Wait for completion still
<-doneCh
case <-doneCh:
}
// write the resulting state to the running op
runningOp.State = newState runningOp.State = newState
if err != nil { if refreshErr != nil {
runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", refreshErr)
return return
} }

View File

@ -171,6 +171,7 @@ func (c *ApplyCommand) Run(args []string) int {
// Perform the operation // Perform the operation
ctx, ctxCancel := context.WithCancel(context.Background()) ctx, ctxCancel := context.WithCancel(context.Background())
defer ctxCancel() defer ctxCancel()
op, err := b.Operation(ctx, opReq) op, err := b.Operation(ctx, opReq)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err)) c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err))
@ -183,11 +184,6 @@ func (c *ApplyCommand) Run(args []string) int {
// Cancel our context so we can start gracefully exiting // Cancel our context so we can start gracefully exiting
ctxCancel() ctxCancel()
// notify tests that the command context was canceled
if testShutdownHook != nil {
testShutdownHook()
}
// Notify the user // Notify the user
c.Ui.Output(outputInterrupt) c.Ui.Output(outputInterrupt)

View File

@ -825,14 +825,7 @@ func TestApply_refresh(t *testing.T) {
func TestApply_shutdown(t *testing.T) { func TestApply_shutdown(t *testing.T) {
cancelled := false cancelled := false
cancelDone := make(chan struct{}) stopped := make(chan struct{})
testShutdownHook = func() {
cancelled = true
close(cancelDone)
}
defer func() {
testShutdownHook = nil
}()
statePath := testTempFile(t) statePath := testTempFile(t)
p := testProvider() p := testProvider()
@ -847,6 +840,12 @@ func TestApply_shutdown(t *testing.T) {
}, },
} }
p.StopFn = func() error {
close(stopped)
cancelled = true
return nil
}
p.DiffFn = func( p.DiffFn = func(
*terraform.InstanceInfo, *terraform.InstanceInfo,
*terraform.InstanceState, *terraform.InstanceState,
@ -864,11 +863,11 @@ func TestApply_shutdown(t *testing.T) {
*terraform.InstanceState, *terraform.InstanceState,
*terraform.InstanceDiff) (*terraform.InstanceState, error) { *terraform.InstanceDiff) (*terraform.InstanceState, error) {
// only cancel once
if !cancelled { if !cancelled {
shutdownCh <- struct{}{} shutdownCh <- struct{}{}
<-cancelDone <-stopped
} }
return &terraform.InstanceState{ return &terraform.InstanceState{
ID: "foo", ID: "foo",
Attributes: map[string]string{ Attributes: map[string]string{
@ -898,10 +897,6 @@ func TestApply_shutdown(t *testing.T) {
if state == nil { if state == nil {
t.Fatal("state should not be nil") t.Fatal("state should not be nil")
} }
if len(state.RootModule().Resources) != 1 {
t.Fatalf("bad: %d", len(state.RootModule().Resources))
}
} }
func TestApply_state(t *testing.T) { func TestApply_state(t *testing.T) {

View File

@ -641,7 +641,3 @@ func isAutoVarFile(path string) bool {
return strings.HasSuffix(path, ".auto.tfvars") || return strings.HasSuffix(path, ".auto.tfvars") ||
strings.HasSuffix(path, ".auto.tfvars.json") strings.HasSuffix(path, ".auto.tfvars.json")
} }
// testShutdownHook is used by tests to verify that a command context has been
// canceled
var testShutdownHook func()

View File

@ -118,11 +118,6 @@ func (c *PlanCommand) Run(args []string) int {
// Cancel our context so we can start gracefully exiting // Cancel our context so we can start gracefully exiting
ctxCancel() ctxCancel()
// notify tests that the command context was canceled
if testShutdownHook != nil {
testShutdownHook()
}
// Notify the user // Notify the user
c.Ui.Output(outputInterrupt) c.Ui.Output(outputInterrupt)

View File

@ -833,14 +833,7 @@ func TestPlan_detailedExitcode_emptyDiff(t *testing.T) {
func TestPlan_shutdown(t *testing.T) { func TestPlan_shutdown(t *testing.T) {
cancelled := false cancelled := false
cancelDone := make(chan struct{}) stopped := make(chan struct{})
testShutdownHook = func() {
cancelled = true
close(cancelDone)
}
defer func() {
testShutdownHook = nil
}()
shutdownCh := make(chan struct{}) shutdownCh := make(chan struct{})
p := testProvider() p := testProvider()
@ -853,6 +846,12 @@ func TestPlan_shutdown(t *testing.T) {
}, },
} }
p.StopFn = func() error {
close(stopped)
cancelled = true
return nil
}
p.DiffFn = func( p.DiffFn = func(
*terraform.InstanceInfo, *terraform.InstanceInfo,
*terraform.InstanceState, *terraform.InstanceState,
@ -860,7 +859,7 @@ func TestPlan_shutdown(t *testing.T) {
if !cancelled { if !cancelled {
shutdownCh <- struct{}{} shutdownCh <- struct{}{}
<-cancelDone <-stopped
} }
return &terraform.InstanceDiff{ return &terraform.InstanceDiff{

View File

@ -196,13 +196,14 @@ func (p *MockResourceProvider) Diff(
info *InstanceInfo, info *InstanceInfo,
state *InstanceState, state *InstanceState,
desired *ResourceConfig) (*InstanceDiff, error) { desired *ResourceConfig) (*InstanceDiff, error) {
p.Lock()
defer p.Unlock()
p.Lock()
p.DiffCalled = true p.DiffCalled = true
p.DiffInfo = info p.DiffInfo = info
p.DiffState = state p.DiffState = state
p.DiffDesired = desired p.DiffDesired = desired
p.Unlock()
if p.DiffFn != nil { if p.DiffFn != nil {
return p.DiffFn(info, state, desired) return p.DiffFn(info, state, desired)
} }