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:
commit
12b7dac124
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue