command: "terraform apply" uses interactive confirmation by default

In the 0.10 release we added an opt-in mode where Terraform would prompt
interactively for confirmation during apply. We made this opt-in to give
those who wrap Terraform in automation some time to update their scripts
to explicitly opt out of this behavior where appropriate.

Here we switch the default so that a "terraform apply" with no arguments
will -- if it computes a non-empty diff -- display the diff and wait for
the user to type "yes" in similar vein to the "terraform destroy" command.

This makes the commonly-used "terraform apply" a safe workflow for
interactive use, so "terraform plan" is now mainly for use in automation
where a separate planning step is used. The apply command remains
non-interactive when given an explicit plan file.

The previous behavior -- though not recommended -- can be obtained by
explicitly setting the -auto-approve option on the apply command line,
and indeed that is how all of the tests are updated here so that they can
continue to run non-interactively.
This commit is contained in:
Martin Atkins 2017-10-30 13:33:27 -07:00
parent 0265b1c0fe
commit 400038eda4
14 changed files with 147 additions and 137 deletions

View File

@ -47,7 +47,7 @@ func (c *ApplyCommand) Run(args []string) int {
}
cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
if !c.Destroy {
cmdFlags.BoolVar(&autoApprove, "auto-approve", true, "skip interactive approval of plan before applying")
cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying")
}
cmdFlags.IntVar(
&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
@ -119,11 +119,6 @@ func (c *ApplyCommand) Run(args []string) int {
if plan != nil {
// Reset the config path for backend loading
configPath = ""
if !autoApprove {
c.Ui.Error("Cannot combine -auto-approve=false with a plan file.")
return 1
}
}
// Load the module if we don't have one yet (not running from plan)
@ -253,12 +248,6 @@ Usage: terraform apply [options] [DIR-OR-PLAN]
configuration or an execution plan can be provided. Execution plans can be
used to only execute a pre-determined set of actions.
DIR can also be a SOURCE as given to the "init" command. In this case,
apply behaves as though "init" was called followed by "apply". This only
works for sources that aren't files, and only if the current working
directory is empty of Terraform files. This is a shortcut for getting
started.
Options:
-backup=path Path to backup the existing state file before
@ -269,9 +258,7 @@ Options:
-lock-timeout=0s Duration to retry a state lock.
-auto-approve=true Skip interactive approval of plan before applying. In a
future version of Terraform, this flag's default value
will change to false.
-auto-approve Skip interactive approval of plan before applying.
-input=true Ask for input for variables if not directly set.

View File

@ -35,6 +35,7 @@ func TestApply(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
@ -72,6 +73,7 @@ func TestApply_lockedState(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code == 0 {
@ -113,6 +115,7 @@ func TestApply_lockedStateWait(t *testing.T) {
args := []string{
"-state", statePath,
"-lock-timeout", "4s",
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
@ -186,6 +189,7 @@ func TestApply_parallelism(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
fmt.Sprintf("-parallelism=%d", par),
testFixturePath("parallelism"),
}
@ -239,6 +243,7 @@ func TestApply_configInvalid(t *testing.T) {
args := []string{
"-state", testTempFile(t),
"-auto-approve",
testFixturePath("apply-config-invalid"),
}
if code := c.Run(args); code != 1 {
@ -281,6 +286,7 @@ func TestApply_defaultState(t *testing.T) {
serial := localState.State().Serial
args := []string{
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
@ -344,6 +350,7 @@ func TestApply_error(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply-error"),
}
if code := c.Run(args); code != 1 {
@ -385,6 +392,7 @@ func TestApply_input(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply-input"),
}
if code := c.Run(args); code != 0 {
@ -420,6 +428,7 @@ func TestApply_inputPartial(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
"-var", "foo=foovalue",
testFixturePath("apply-input-partial"),
}
@ -460,6 +469,7 @@ func TestApply_noArgs(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
@ -783,6 +793,7 @@ func TestApply_refresh(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
@ -875,6 +886,7 @@ func TestApply_shutdown(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply-shutdown"),
}
if code := c.Run(args); code != 0 {
@ -934,6 +946,7 @@ func TestApply_state(t *testing.T) {
// Run the apply command pointing to our existing state
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
@ -1008,6 +1021,7 @@ func TestApply_sensitiveOutput(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply-sensitive-output"),
}
@ -1040,6 +1054,7 @@ func TestApply_stateFuture(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code == 0 {
@ -1071,6 +1086,7 @@ func TestApply_statePast(t *testing.T) {
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
@ -1103,6 +1119,7 @@ func TestApply_vars(t *testing.T) {
}
args := []string{
"-auto-approve",
"-var", "foo=bar",
"-state", statePath,
testFixturePath("apply-vars"),
@ -1146,6 +1163,7 @@ func TestApply_varFile(t *testing.T) {
}
args := []string{
"-auto-approve",
"-var-file", varFilePath,
"-state", statePath,
testFixturePath("apply-vars"),
@ -1199,6 +1217,7 @@ func TestApply_varFileDefault(t *testing.T) {
}
args := []string{
"-auto-approve",
"-state", statePath,
testFixturePath("apply-vars"),
}
@ -1251,6 +1270,7 @@ func TestApply_varFileDefaultJSON(t *testing.T) {
}
args := []string{
"-auto-approve",
"-state", statePath,
testFixturePath("apply-vars"),
}
@ -1303,6 +1323,7 @@ func TestApply_backup(t *testing.T) {
// Run the apply command pointing to our existing state
args := []string{
"-auto-approve",
"-state", statePath,
"-backup", backupPath,
testFixturePath("apply"),
@ -1353,6 +1374,7 @@ func TestApply_disableBackup(t *testing.T) {
// Run the apply command pointing to our existing state
args := []string{
"-auto-approve",
"-state", statePath,
"-backup", "-",
testFixturePath("apply"),
@ -1411,6 +1433,7 @@ func TestApply_terraformEnv(t *testing.T) {
}
args := []string{
"-auto-approve",
"-state", statePath,
testFixturePath("apply-terraform-env"),
}
@ -1466,6 +1489,7 @@ func TestApply_terraformEnvNonDefault(t *testing.T) {
}
args := []string{
"-auto-approve",
testFixturePath("apply-terraform-env"),
}
if code := c.Run(args); code != 0 {

View File

@ -149,7 +149,7 @@ func TestAutoApplyInAutomation(t *testing.T) {
}
//// APPLY
stdout, stderr, err = tf.Run("apply", "-input=false", "-auto-approve=true")
stdout, stderr, err = tf.Run("apply", "-input=false", "-auto-approve")
if err != nil {
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
}

View File

@ -18,13 +18,9 @@ Usage: `terraform apply [options] [dir-or-plan]`
By default, `apply` scans the current directory for the configuration
and applies the changes appropriately. However, a path to another configuration
or an execution plan can be provided. Execution plans can be used to only
execute a pre-determined set of actions.
The `dir` argument can also be a [module source](/docs/modules/index.html).
In this case, `apply` behaves as though `init` were called with that
argument followed by an `apply` in the current directory. This is meant
as a shortcut for getting started.
or an execution plan can be provided. Explicit execution plans files can be
used to split plan and apply into separate steps within
[automation systems](/guides/running-terraform-in-automation.html).
The command-line flags are all optional. The list of available flags are:
@ -37,8 +33,7 @@ The command-line flags are all optional. The list of available flags are:
* `-input=true` - Ask for input for variables if not directly set.
* `-auto-approve=true` - Skip interactive approval of plan before applying. In a
future version of Terraform, this flag's default value will change to false.
* `-auto-approve` - Skip interactive approval of plan before applying.
* `-no-color` - Disables output with coloring.

View File

@ -11,8 +11,17 @@ description: |-
The `terraform plan` command is used to create an execution plan. Terraform
performs a refresh, unless explicitly disabled, and then determines what
actions are necessary to achieve the desired state specified in the
configuration files. The plan can be saved using `-out`, and then provided
to `terraform apply` to ensure only the pre-planned actions are executed.
configuration files.
This command is a convenient way to check whether the execution plan for a
set of changes matches your expectations without making any changes to
real resources or to the state. For example, `terraform plan` might be run
before committing a change to version control, to create confidence that it
will behave as expected.
The optional `-out` argument can be used to save the generated plan to a file
for later execution with `terraform apply`, which can be useful when
[running Terraform in automation](/guides/running-terraform-in-automation.html).
## Usage

View File

@ -174,15 +174,15 @@ Where manual approval is not required, a simpler sequence of commands
can be used:
* `terraform init -input=false`
* `terraform apply -input=false -auto-approve=true`
* `terraform apply -input=false -auto-approve`
This variant of the `apply` command implicitly creates a new plan and then
immediately applies it. The `-auto-approve=true` option tells Terraform not
immediately applies it. The `-auto-approve` option tells Terraform not
to require interactive approval of the plan before applying it.
~> When Terraform is empowered to make destructive changes to infrastructure,
manual review of plans is always recommended unless downtime is tolerated
in the event of unintended changes. Use automatic apply **only** with
in the event of unintended changes. Use automatic approval **only** with
non-critical infrastructure.
## Testing Pull Requests with `terraform plan`

View File

@ -165,16 +165,19 @@ specifying that version in configuration to ensure that running
is not necessary for following the getting started guide, since this
configuration will be discarded at the end.
## Execution Plan
## Apply Changes
Next, let's see what Terraform would do if we asked it to
apply this configuration. In the same directory as the
`example.tf` file you created, run `terraform plan`. You
should see output similar to what is copied below. We've
truncated some of the output to save space.
~> **Note:** The commands shown in this guide apply to Terraform 0.11 and
above. Earlier versions require using the `terraform plan` command to
see the execution plan before applying it. Use `terraform version`
to confirm your running version.
In the same directory as the `example.tf` file you created, run
`terraform apply`. You should see output similar to below, though we've
truncated some of the output to save space:
```
$ terraform plan
$ terraform apply
# ...
+ aws_instance.example
@ -198,30 +201,31 @@ $ terraform plan
vpc_security_group_ids.#: "<computed>"
```
`terraform plan` shows what changes Terraform will apply to
your infrastructure given the current state of your infrastructure
as well as the current contents of your configuration.
If `terraform plan` failed with an error, read the error message
and fix the error that occurred. At this stage, it is probably a
syntax error in the configuration.
This output shows the _execution plan_, describing which actions Terraform
will take in order to change real infrastructure to match the configuration.
The output format is similar to the diff format generated by tools
such as Git. The output has a "+" next to "aws\_instance.example",
such as Git. The output has a `+` next to `aws_instance.example`,
meaning that Terraform will create this resource. Beneath that,
it shows the attributes that will be set. When the value displayed
is `<computed>`, it means that the value won't be known
until the resource is created.
## Apply
The plan looks good, our configuration appears valid, so it's time to
create real resources. Run `terraform apply` in the same directory
as your `example.tf`, and watch it go! It will take a few minutes
since Terraform waits for the EC2 instance to become available.
If `terraform apply` failed with an error, read the error message
and fix the error that occurred. At this stage, it is likely to be a
syntax error in the configuration.
If the plan was created successfully, Terraform will now pause and wait for
approval before proceeding. If anything in the plan seems incorrect or
dangerous, it is safe to abort here with no changes made to your infrastructure.
In this case the plan looks acceptable, so type `yes` at the confirmation
prompt to proceed.
Executing the plan will take a few minutes since Terraform waits for the EC2
instance to become available:
```
$ terraform apply
# ...
aws_instance.example: Creating...
ami: "" => "ami-2757f631"
instance_type: "" => "t2.micro"
@ -235,20 +239,19 @@ Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
# ...
```
Done! You can go to the AWS console to prove to yourself that the
EC2 instance has been created.
After this, Terraform is all done! You can go to the EC2 console to see the
created EC2 instance. (Make sure you're looking at the same region that was
configured in the provider configuration!)
Terraform also puts some state into the `terraform.tfstate` file
by default. This state file is extremely important; it maps various
resource metadata to actual resource IDs so that Terraform knows
what it is managing. This file must be saved and distributed
to anyone who might run Terraform. It is generally recommended to
Terraform also wrote some data into the `terraform.tfstate` file. This state
file is extremely important; it keeps track of the IDs of created resources
so that Terraform knows what it is managing. This file must be saved and
distributed to anyone who might run Terraform. It is generally recommended to
[setup remote state](https://www.terraform.io/docs/state/remote.html)
when working with Terraform. This will mean that any potential secrets
stored in the state file, will not be checked into version control
when working with Terraform, to share the state automatically, but this is
not necessary for simple situations like this Getting Started guide.
You can inspect the state using `terraform show`:
You can inspect the current state using `terraform show`:
```
$ terraform show
@ -267,8 +270,8 @@ aws_instance.example:
```
You can see that by creating our resource, we've also gathered
a lot more metadata about it. This metadata can actually be referenced
for other resources or outputs, which will be covered later in
a lot of information about it. These values can actually be referenced
to configure other resources or outputs, which will be covered later in
the getting started guide.
## Provisioning
@ -279,8 +282,7 @@ an image-based infrastructure (perhaps creating images with
[Packer](https://www.packer.io)), then this is all you need.
However, many infrastructures still require some sort of initialization
or software provisioning step. Terraform supports
provisioners,
or software provisioning step. Terraform supports provisioners,
which we'll cover a little bit later in the getting started guide,
in order to do this.

View File

@ -40,12 +40,13 @@ an Ubuntu 16.10 AMI. Terraform configurations are meant to be
changed like this. You can also completely remove resources
and Terraform will know to destroy the old one.
## Execution Plan
## Apply Changes
Let's see what Terraform will do with the change we made.
After changing the configuration, run `terraform apply` again to see how
Terraform will apply this change to the existing resources.
```
$ terraform plan
$ terraform apply
# ...
-/+ aws_instance.example
@ -63,25 +64,23 @@ $ terraform plan
vpc_security_group_ids.#: "1" => "<computed>"
```
The prefix "-/+" means that Terraform will destroy and recreate
the resource, versus purely updating it in-place. While some attributes
can do in-place updates (which are shown with a "~" prefix), AMI
changing on EC2 instance requires a new resource. Terraform handles
these details for you, and the execution plan makes it clear what
Terraform will do.
The prefix `-/+` means that Terraform will destroy and recreate
the resource, rather than updating it in-place. While some attributes
can be updated in-place (which are shown with the `~` prefix), changing the
AMI for an EC2 instance requires recreating it. Terraform handles these details
for you, and the execution plan makes it clear what Terraform will do.
Additionally, the plan output shows that the AMI change is what
necessitated the creation of a new resource. Using this information,
you can tweak your changes to possibly avoid destroy/create updates
if you didn't want to do them at this time.
Additionally, the execution plan shows that the AMI change is what
required resource to be replaced. Using this information,
you can adjust your changes to possibly avoid destroy/create updates
if they are not acceptable in some situations.
## Apply
Once again, Terraform prompts for approval of the execution plan before
proceeding. Answer `yes` to execute the planned steps:
From the plan, we know what will happen. Let's apply and enact
the change.
```
$ terraform apply
# ...
aws_instance.example: Refreshing state... (ID: i-64c268fe)
aws_instance.example: Destroying...
aws_instance.example: Destruction complete
@ -113,9 +112,9 @@ Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
# ...
```
As the plan predicted, Terraform started by destroying our old
instance, then creating the new one. You can use `terraform show`
again to see the new properties associated with this instance.
As indicated by the execution plan, Terraform first destroyed the existing
instance and then created a new one in its place. You can use `terraform show`
again to see the new values associated with this instance.
## Next

View File

@ -52,13 +52,13 @@ The syntax for this interpolation should be straightforward:
it requests the "id" attribute from the "aws\_instance.example"
resource.
## Plan and Execute
## Apply Changes
Run `terraform plan` to view the execution plan. The output
will look something like the following:
Run `terraform apply` to see how Terraform plans to apply this change.
The output will look similar to the following:
```
$ terraform plan
$ terraform apply
+ aws_eip.ip
allocation_id: "<computed>"
@ -96,11 +96,12 @@ raw interpolation is still present. This is because this variable
won't be known until the "aws\_instance" is created. It will be
replaced at apply-time.
Next, run `terraform apply`. The output will look similar to the
As usual, Terraform prompts for confirmation before making any changes.
Answer `yes` to apply. The continued output will look similar to the
following:
```
$ terraform apply
# ...
aws_instance.example: Creating...
ami: "" => "ami-b374d5a5"
instance_type: "" => "t2.micro"
@ -120,12 +121,10 @@ aws_eip.ip: Creation complete
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```
It is clearer to see from actually running Terraform, but
Terraform creates the EC2 instance before the elastic IP
address. Due to the interpolation earlier where the elastic
IP requires the ID of the EC2 instance, Terraform is able
to infer a dependency, and knows to create the instance
first.
As shown above, Terraform created the EC2 instance before creating the Elastic
IP address. Due to the interpolation expression that passes the ID of the EC2
instance to the Elastic IP address, Terraform is able to infer a dependency,
and knows it must create the instance first.
## Implicit and Explicit Dependencies

View File

@ -18,29 +18,27 @@ environments. But if you're using Terraform to spin up multiple
environments such as development, test, QA environments, then
destroying is a useful action.
## Plan
## Destroy
Before destroying our infrastructure, we can use the plan command
to see what resources Terraform will destroy.
Resources can be destroyed using the `terraform destroy` command, which is
similar to `terraform apply` but it behaves as if all of the resources have
been removed from the configuration.
```
$ terraform plan -destroy
$ terraform destroy
# ...
- aws_instance.example
```
With the `-destroy` flag, we're asking Terraform to plan a destroy,
where all resources under Terraform management are destroyed. You can
use this output to verify exactly what resources Terraform is managing
and will destroy.
The `-` prefix indicates that a the instance will be destroyed. As with apply,
Terraform shows its execution plan and waits for approval before making any
changes.
## Destroy
Let's destroy the infrastructure now:
Answer `yes` to execute this plan and destroy the infrastructure:
```
$ terraform destroy
# ...
aws_instance.example: Destroying...
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
@ -48,15 +46,11 @@ Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
# ...
```
The `terraform destroy` command should ask you to verify that you
really want to destroy the infrastructure. Terraform only accepts the
literal "yes" as an answer as a safety mechanism. Once entered, Terraform
will go through and destroy the infrastructure.
Just like with `apply`, Terraform is smart enough to determine what order
things should be destroyed. In our case, we only had one resource, so there
wasn't any ordering necessary. But in more complicated cases with multiple
resources, Terraform will destroy in the proper order.
Just like with `apply`, Terraform determines the order in which
things must be destroyed. In this case there was only one resource, so no
ordering was necessary. In more complicated cases with multiple resources,
Terraform will destroy them in a suitable order to respect dependencies,
as we'll see later in this guide.
## Next

View File

@ -89,10 +89,10 @@ updates.
## Planning and Apply Modules
With the modules downloaded, we can now plan and apply it. If you run
`terraform plan`, you should see output similar to below:
`terraform apply`, you should see output similar to below:
```
$ terraform plan
$ terraform apply
# ...
+ module.consul.aws_instance.server.0
# ...
@ -106,18 +106,16 @@ $ terraform plan
Plan: 4 to add, 0 to change, 0 to destroy.
```
Conceptually, the module is treated like a black box. In the plan, however
Conceptually, the module is treated like a black box. In the plan however,
Terraform shows each resource the module manages so you can see each detail
about what the plan will do. If you'd like compressed plan output, you can
specify the `-module-depth=` flag to get Terraform to output summaries by
module.
about what actions the plan will take.
Next, run `terraform apply` to create the module. Note that as we warned above,
As usual, Terraform waits for confirmation before making any changes. Answer
`yes` to create the resources from the module. Note that, as we warned above,
the resources this module creates are outside of the AWS free tier, so this
will have some cost associated with it.
```
$ terraform apply
# ...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
```

View File

@ -60,6 +60,8 @@ then run `apply`:
```
$ terraform apply
# ...
aws_instance.example: Creating...
ami: "" => "ami-b374d5a5"
instance_type: "" => "t2.micro"

View File

@ -66,11 +66,11 @@ configuring a backend, run `terraform init` to setup Terraform. It should
ask if you want to migrate your state to Consul. Say "yes" and Terraform
will copy your state.
Now, if you run `terraform plan`, Terraform should state that there are
Now, if you run `terraform apply`, Terraform should state that there are
no changes:
```
$ terraform plan
$ terraform apply
# ...
No changes. Infrastructure is up-to-date.

View File

@ -66,7 +66,7 @@ You can set variables directly on the command-line with the
accepts this flag, such as `apply`, `plan`, and `refresh`:
```
$ terraform plan \
$ terraform apply \
-var 'access_key=foo' \
-var 'secret_key=bar'
# ...
@ -100,7 +100,7 @@ You can use multiple `-var-file` arguments in a single command, with some
checked in to version control and others not checked in. For example:
```
$ terraform plan \
$ terraform apply \
-var-file="secret.tfvars" \
-var-file="production.tfvars"
```
@ -116,10 +116,11 @@ List and map type variables must be populated via one of the other mechanisms.
#### UI Input
If you execute `terraform plan` or apply without doing anything,
Terraform will ask you to input the variables interactively. These
variables are not saved, but provides a nice user experience for getting
started with Terraform.
If you execute `terraform apply` with certain variables unspecified,
Terraform will ask you to input their values interactively. These
values are not saved, but this provides a convenient workflow when getting
started with Terraform. UI Input is not recommended for everyday use of
Terraform.
-> **Note**: UI Input is only supported for string variables. List and map
variables must be populated via one of the other mechanisms.
@ -199,7 +200,7 @@ We set defaults above, but maps can also be set using the `-var` and
`-var-file` values. For example:
```
$ terraform plan -var 'amis={ us-east-1 = "foo", us-west-2 = "bar" }'
$ terraform apply -var 'amis={ us-east-1 = "foo", us-west-2 = "bar" }'
# ...
```