commit
74f0842c13
127
command/apply.go
127
command/apply.go
|
@ -17,6 +17,11 @@ import (
|
|||
type ApplyCommand struct {
|
||||
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{}
|
||||
}
|
||||
|
||||
|
@ -26,7 +31,12 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
|
||||
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.StringVar(&statePath, "state", DefaultStateFilename, "path")
|
||||
cmdFlags.StringVar(&stateOutPath, "state-out", "", "path")
|
||||
|
@ -70,22 +80,24 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
backupPath = stateOutPath + DefaultBackupExtention
|
||||
}
|
||||
|
||||
// Do a detect to determine if we need to do an init + apply.
|
||||
if detected, err := module.Detect(configPath, pwd); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Invalid path: %s", err))
|
||||
return 1
|
||||
} else if !strings.HasPrefix(detected, "file") {
|
||||
// If this isn't a file URL then we're doing an init +
|
||||
// apply.
|
||||
var init InitCommand
|
||||
init.Meta = c.Meta
|
||||
if code := init.Run([]string{detected}); code != 0 {
|
||||
return code
|
||||
}
|
||||
if !c.Destroy {
|
||||
// Do a detect to determine if we need to do an init + apply.
|
||||
if detected, err := module.Detect(configPath, pwd); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Invalid path: %s", err))
|
||||
return 1
|
||||
} else if !strings.HasPrefix(detected, "file") {
|
||||
// If this isn't a file URL then we're doing an init +
|
||||
// apply.
|
||||
var init InitCommand
|
||||
init.Meta = c.Meta
|
||||
if code := init.Run([]string{detected}); code != 0 {
|
||||
return code
|
||||
}
|
||||
|
||||
// Change the config path to be the cwd
|
||||
configPath = pwd
|
||||
// Change the config path to be the cwd
|
||||
configPath = pwd
|
||||
}
|
||||
}
|
||||
|
||||
// Build the context based on the arguments given
|
||||
|
@ -97,7 +109,29 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if c.Destroy && planned {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Destroy can't be called with a plan file."))
|
||||
return 1
|
||||
}
|
||||
if c.Input() {
|
||||
if c.Destroy {
|
||||
v, err := c.UIInput().Input(&terraform.InputOpts{
|
||||
Id: "destroy",
|
||||
Query: "Do you really want to destroy?",
|
||||
Description: "Terraform will delete all your manage infrastructure.\n" +
|
||||
"There is no undo. Only 'yes' will be accepted to confirm.",
|
||||
})
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
|
||||
return 1
|
||||
}
|
||||
if v != "yes" {
|
||||
c.Ui.Output("Destroy cancelled.")
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Input(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
|
||||
return 1
|
||||
|
@ -130,7 +164,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(
|
||||
"Error creating plan: %s", err))
|
||||
return 1
|
||||
|
@ -249,6 +288,22 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
func (c *ApplyCommand) Help() string {
|
||||
if c.Destroy {
|
||||
return c.helpDestroy()
|
||||
}
|
||||
|
||||
return c.helpApply()
|
||||
}
|
||||
|
||||
func (c *ApplyCommand) Synopsis() string {
|
||||
if c.Destroy {
|
||||
return "Destroy Terraform-managed infrastructure"
|
||||
}
|
||||
|
||||
return "Builds or changes infrastructure"
|
||||
}
|
||||
|
||||
func (c *ApplyCommand) helpApply() string {
|
||||
helpText := `
|
||||
Usage: terraform apply [options] [DIR]
|
||||
|
||||
|
@ -293,6 +348,40 @@ Options:
|
|||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *ApplyCommand) Synopsis() string {
|
||||
return "Builds or changes infrastructure"
|
||||
func (c *ApplyCommand) helpDestroy() string {
|
||||
helpText := `
|
||||
Usage: terraform destroy [options] [DIR]
|
||||
|
||||
Destroy Terraform-managed infrastructure.
|
||||
|
||||
Options:
|
||||
|
||||
-backup=path Path to backup the existing state file before
|
||||
modifying. Defaults to the "-state-out" path with
|
||||
".backup" extension. Set to "-" to disable backup.
|
||||
|
||||
-input=true Ask for input for destroy confirmation.
|
||||
|
||||
-no-color If specified, output won't contain any color.
|
||||
|
||||
-refresh=true Update state prior to checking for differences. This
|
||||
has no effect if a plan file is given to apply.
|
||||
|
||||
-state=path Path to read and save state (unless state-out
|
||||
is specified). Defaults to "terraform.tfstate".
|
||||
|
||||
-state-out=path Path to write state to that is different than
|
||||
"-state". This can be used to preserve the old
|
||||
state.
|
||||
|
||||
-var 'foo=bar' Set a variable in the Terraform configuration. This
|
||||
flag can be set multiple times.
|
||||
|
||||
-var-file=foo Set variables in the Terraform configuration from
|
||||
a file. If "terraform.tfvars" is present, it will be
|
||||
automatically loaded if this flag is not specified.
|
||||
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
`
|
|
@ -111,6 +111,13 @@ func (m *Meta) Input() bool {
|
|||
return !test && m.input && len(m.variables) == 0
|
||||
}
|
||||
|
||||
// UIInput returns a UIInput object to be used for asking for input.
|
||||
func (m *Meta) UIInput() terraform.UIInput {
|
||||
return &UIInput{
|
||||
Colorize: m.Colorize(),
|
||||
}
|
||||
}
|
||||
|
||||
// contextOpts returns the options to use to initialize a Terraform
|
||||
// context with the settings from this Meta.
|
||||
func (m *Meta) contextOpts() *terraform.ContextOpts {
|
||||
|
@ -133,9 +140,7 @@ func (m *Meta) contextOpts() *terraform.ContextOpts {
|
|||
vs[k] = v
|
||||
}
|
||||
opts.Variables = vs
|
||||
opts.UIInput = &UIInput{
|
||||
Colorize: m.Colorize(),
|
||||
}
|
||||
opts.UIInput = m.UIInput()
|
||||
|
||||
return &opts
|
||||
}
|
||||
|
|
|
@ -40,6 +40,14 @@ func init() {
|
|||
}, nil
|
||||
},
|
||||
|
||||
"destroy": func() (cli.Command, error) {
|
||||
return &command.ApplyCommand{
|
||||
Meta: meta,
|
||||
Destroy: true,
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
}, nil
|
||||
},
|
||||
|
||||
"get": func() (cli.Command, error) {
|
||||
return &command.GetCommand{
|
||||
Meta: meta,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Command: destroy"
|
||||
sidebar_current: "docs-commands-destroy"
|
||||
---
|
||||
|
||||
# Command: destroy
|
||||
|
||||
The `terraform destroy` command is used to destroy the Terraform-managed
|
||||
infrastructure.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `terraform destroy [options] [dir]`
|
||||
|
||||
Infrastructure managed by Terraform will be destroyed. This will ask for
|
||||
confirmation before destroying.
|
||||
|
||||
This command accepts all the flags that the
|
||||
[apply command](/docs/commands/apply.html) accepts. If `-input=false` is
|
||||
set, then the destroy confirmation will not be shown.
|
|
@ -55,6 +55,10 @@
|
|||
<a href="/docs/commands/apply.html">apply</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-commands-destroy") %>>
|
||||
<a href="/docs/commands/destroy.html">destroy</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-commands-get") %>>
|
||||
<a href="/docs/commands/get.html">get</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue