command/apply: Ctrl-C works

This commit is contained in:
Mitchell Hashimoto 2014-07-02 17:01:02 -07:00
parent f7bc33812e
commit 5aa6ada589
4 changed files with 148 additions and 5 deletions

View File

@ -13,8 +13,9 @@ import (
// ApplyCommand is a Command implementation that applies a Terraform
// configuration and actually builds or changes infrastructure.
type ApplyCommand struct {
TFConfig *terraform.Config
Ui cli.Ui
ShutdownCh chan struct{}
TFConfig *terraform.Config
Ui cli.Ui
}
func (c *ApplyCommand) Run(args []string) int {
@ -63,7 +64,41 @@ func (c *ApplyCommand) Run(args []string) int {
return 1
}
state, err := tf.Apply(plan)
errCh := make(chan error)
stateCh := make(chan *terraform.State)
go func() {
state, err := tf.Apply(plan)
if err != nil {
errCh <- err
return
}
stateCh <- state
}()
err = nil
var state *terraform.State
select {
case <-c.ShutdownCh:
c.Ui.Output("Interrupt received. Gracefully shutting down...")
// Stop execution
tf.Stop()
// Still get the result, since there is still one
select {
case <-c.ShutdownCh:
c.Ui.Error(
"Two interrupts received. Exiting immediately. Note that data\n" +
"loss may have occurred.")
return 1
case state = <-stateCh:
case err = <-errCh:
}
case state = <-stateCh:
case err = <-errCh:
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error applying plan: %s", err))
return 1

View File

@ -85,6 +85,88 @@ func TestApply_plan(t *testing.T) {
}
}
func TestApply_shutdown(t *testing.T) {
stopped := false
stopCh := make(chan struct{})
stopReplyCh := make(chan struct{})
statePath := testTempFile(t)
p := testProvider()
shutdownCh := make(chan struct{})
ui := new(cli.MockUi)
c := &ApplyCommand{
ShutdownCh: shutdownCh,
TFConfig: testTFConfig(p),
Ui: ui,
}
p.DiffFn = func(
*terraform.ResourceState,
*terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
return &terraform.ResourceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"ami": &terraform.ResourceAttrDiff{
New: "bar",
},
},
}, nil
}
p.ApplyFn = func(
*terraform.ResourceState,
*terraform.ResourceDiff) (*terraform.ResourceState, error) {
if !stopped {
stopped = true
close(stopCh)
<-stopReplyCh
}
return &terraform.ResourceState{
ID: "foo",
Attributes: map[string]string{
"ami": "2",
},
}, nil
}
go func() {
<-stopCh
shutdownCh <- struct{}{}
close(stopReplyCh)
}()
args := []string{
"-init",
statePath,
testFixturePath("apply-shutdown"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
if _, err := os.Stat(statePath); err != nil {
t.Fatalf("err: %s", err)
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
defer f.Close()
state, err := terraform.ReadState(f)
if err != nil {
t.Fatalf("err: %s", err)
}
if state == nil {
t.Fatal("state should not be nil")
}
if len(state.Resources) != 1 {
t.Fatalf("bad: %d", len(state.Resources))
}
}
func TestApply_state(t *testing.T) {
originalState := &terraform.State{
Resources: map[string]*terraform.ResourceState{

View File

@ -0,0 +1,7 @@
resource "test_instance" "foo" {
ami = "bar"
}
resource "test_instance" "bar" {
ami = "${test_instance.foo.ami}"
}

View File

@ -2,6 +2,7 @@ package main
import (
"os"
"os/signal"
"github.com/hashicorp/terraform/command"
"github.com/mitchellh/cli"
@ -28,8 +29,9 @@ func init() {
Commands = map[string]cli.CommandFactory{
"apply": func() (cli.Command, error) {
return &command.ApplyCommand{
TFConfig: &TFConfig,
Ui: Ui,
ShutdownCh: makeShutdownCh(),
TFConfig: &TFConfig,
Ui: Ui,
}, nil
},
@ -64,3 +66,20 @@ func init() {
},
}
}
// makeShutdownCh creates an interrupt listener and returns a channel.
// A message will be sent on the channel for every interrupt received.
func makeShutdownCh() <-chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}