command/destroy: first steps

This commit is contained in:
Mitchell Hashimoto 2014-09-30 21:49:24 -07:00
parent 3ae6f6ba76
commit ab9dd71bcb
2 changed files with 159 additions and 17 deletions

View File

@ -17,6 +17,11 @@ import (
type ApplyCommand struct { type ApplyCommand struct {
Meta Meta
// If true, then this apply command will become the "destroy"
// command. It is just like apply but only processes a destroy.
Destroy bool
// When this channel is closed, the apply will be cancelled.
ShutdownCh <-chan struct{} ShutdownCh <-chan struct{}
} }
@ -26,7 +31,12 @@ func (c *ApplyCommand) Run(args []string) int {
args = c.Meta.process(args, true) args = c.Meta.process(args, true)
cmdFlags := c.Meta.flagSet("apply") cmdName := "apply"
if c.Destroy {
cmdName = "destroy"
}
cmdFlags := c.Meta.flagSet(cmdName)
cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&stateOutPath, "state-out", "", "path") cmdFlags.StringVar(&stateOutPath, "state-out", "", "path")
@ -70,22 +80,24 @@ func (c *ApplyCommand) Run(args []string) int {
backupPath = stateOutPath + DefaultBackupExtention backupPath = stateOutPath + DefaultBackupExtention
} }
// Do a detect to determine if we need to do an init + apply. if !c.Destroy {
if detected, err := module.Detect(configPath, pwd); err != nil { // Do a detect to determine if we need to do an init + apply.
c.Ui.Error(fmt.Sprintf( if detected, err := module.Detect(configPath, pwd); err != nil {
"Invalid path: %s", err)) c.Ui.Error(fmt.Sprintf(
return 1 "Invalid path: %s", err))
} else if !strings.HasPrefix(detected, "file") { return 1
// If this isn't a file URL then we're doing an init + } else if !strings.HasPrefix(detected, "file") {
// apply. // If this isn't a file URL then we're doing an init +
var init InitCommand // apply.
init.Meta = c.Meta var init InitCommand
if code := init.Run([]string{detected}); code != 0 { init.Meta = c.Meta
return code if code := init.Run([]string{detected}); code != 0 {
} return code
}
// Change the config path to be the cwd // Change the config path to be the cwd
configPath = pwd configPath = pwd
}
} }
// Build the context based on the arguments given // Build the context based on the arguments given
@ -97,6 +109,11 @@ func (c *ApplyCommand) Run(args []string) int {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
if planned {
c.Ui.Error(fmt.Sprintf(
"Destroy can't be called with a plan file."))
return 1
}
if c.Input() { if c.Input() {
if err := ctx.Input(); err != nil { if err := ctx.Input(); err != nil {
c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
@ -130,7 +147,12 @@ func (c *ApplyCommand) Run(args []string) int {
} }
} }
if _, err := ctx.Plan(nil); err != nil { var opts terraform.PlanOpts
if c.Destroy {
opts.Destroy = true
}
if _, err := ctx.Plan(&opts); err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"Error creating plan: %s", err)) "Error creating plan: %s", err))
return 1 return 1

View File

@ -0,0 +1,120 @@
package command
import (
"os"
"strings"
"testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
func TestApply_destroy(t *testing.T) {
originalState := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
},
},
},
},
},
}
statePath := testStateFile(t, originalState)
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Destroy: true,
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
// Run the apply command pointing to our existing state
args := []string{
"-state", statePath,
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Verify a new state exists
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")
}
actualStr := strings.TrimSpace(state.String())
expectedStr := strings.TrimSpace(testApplyDestroyStr)
if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
}
// Should have a backup file
f, err = os.Open(statePath + DefaultBackupExtention)
if err != nil {
t.Fatalf("err: %s", err)
}
backupState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actualStr = strings.TrimSpace(backupState.String())
expectedStr = strings.TrimSpace(originalState.String())
if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
}
}
func TestApply_destroyPlan(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{
Module: testModule(t, "apply"),
})
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Destroy: true,
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
// Run the apply command pointing to our existing state
args := []string{
planPath,
}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
const testApplyDestroyStr = `
<no state>
`