gruntwork-io / terragrunt

Terragrunt is a flexible orchestration tool that allows Infrastructure as Code written in OpenTofu/Terraform to scale.
https://terragrunt.gruntwork.io/
MIT License
7.96k stars 967 forks source link

Unable to run Plan and Apply on different machines #494

Open dotjim opened 6 years ago

dotjim commented 6 years ago

Terragrunt appears to not support operation via Continuous Integration in the Plan and Apply on different machines scenario described by Terraform.

Attempts to have Terragrunt plan-all commands produce a plan output file, that would later be used as an input file by apply in another CI phase (or possibly another machine) fail with Error: error satisfying plugin requirements as no plugins are seen to be installed.

I've attempted to address via the TF_PLUGIN_CACHE_DIR environment variable, with the intention of having the plugin cache directory produced by the plan-all phase be part of the CI artifact that is subsequently used in the apply phase. Results suggest Terragrunt might not fully support TF_PLUGIN_CACHE_DIR.

Environment summary

Terragrunt project structure mirrors Example infrastructure-live for Terragrunt Tests below performed on Mac with Terragrunt v0.14.11 and Terraform v0.11.7

Steps to reproduce

$ rm -fr ~/.terragrunt/
$ export TF_PLUGIN_CACHE_DIR="/development/plugin-cache"
$ mkdir $TF_PLUGIN_CACHE_DIR
$ cd /development/infrastructure/<region>/<env>/
$ terragrunt plan-all -out=/development/tfplan -input=false --terragrunt-source /development/infrastructure-modules/
[...]
Plan: 3 to add, 2 to change, 2 to destroy.

------------------------------------------------------------------------

This plan was saved to: /development/tfplan
[...]

The above results in the expected plan file being produced, and /development/plugin-cache populated with relevant plugins.

However attempts to use the plan file produced fail, even from the same machine and working directory as above. For example:

$ terragrunt apply -lock=true --terragrunt-source /development/infrastructure-modules/ /development/tfplan

[terragrunt] [/development/infrastructure/<region>/<env>] 2018/06/27 19:38:13 Running command: terraform --version
[terragrunt] 2018/06/27 19:38:13 Reading Terragrunt config file at /development/infrastructure/<region>/<env>/terraform.tfvars
[terragrunt] 2018/06/27 19:38:13 WARNING: no double-slash (//) found in source URL /development/infrastructure-modules/. Relative paths in downloaded Terraform code may not work.
[terragrunt] 2018/06/27 19:38:13 Cleaning up existing *.tf files in /Users/<user>/.terragrunt/6dOzorUGLXs9_tX5Tfk4ijN6t6E/7KyfyvIHVj_sGgjDjciUZgZWEws
[terragrunt] 2018/06/27 19:38:13 Downloading Terraform configurations from file:///development/infrastructure-modules into /Users/<user>/.terragrunt/6dOzorUGLXs9_tX5Tfk4ijN6t6E/7KyfyvIHVj_sGgjDjciUZgZWEws using terraform init
[terragrunt] [/development/infrastructure/<region>/<env>] 2018/06/27 19:38:13 Initializing remote state for the s3 backend
[terragrunt] [/development/infrastructure/<region>/<env>] 2018/06/27 19:38:15 Running command: terraform init -backend-config=bucket=terraform-state-<env> -backend-config=key=./terraform.tfstate -backend-config=region=<region> -backend-config=dynamodb_table=terraform-locks-<env> -backend-config=encrypt=true -from-module=file:///development/infrastructure-modules /Users/<user>/.terragrunt/6dOzorUGLXs9_tX5Tfk4ijN6t6E/7KyfyvIHVj_sGgjDjciUZgZWEws
Copying configuration from "file:///development/infrastructure-modules"...
Terraform initialized in an empty directory!

The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.
[terragrunt] 2018/06/27 19:38:15 Copying files from /development/infrastructure/<region>/<env> into /Users/<user>/.terragrunt/6dOzorUGLXs9_tX5Tfk4ijN6t6E/7KyfyvIHVj_sGgjDjciUZgZWEws
[terragrunt] 2018/06/27 19:38:15 Setting working directory to /Users/<user>/.terragrunt/6dOzorUGLXs9_tX5Tfk4ijN6t6E/7KyfyvIHVj_sGgjDjciUZgZWEws
[terragrunt] 2018/06/27 19:38:15 Skipping var-file /development/infrastructure/<region>/<env>/ignore as it does not exist
[terragrunt] [/development/infrastructure/<region>/<env>] 2018/06/27 19:38:15 Initializing remote state for the s3 backend
[terragrunt] [/development/infrastructure/<region>/<env>] 2018/06/27 19:38:16 Running command: terraform init -backend-config=region=<region> -backend-config=dynamodb_table=terraform-locks-<env> -backend-config=encrypt=true -backend-config=bucket=terraform-state-<env> -backend-config=key=./terraform.tfstate
Terraform initialized in an empty directory!

The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.
[terragrunt] 2018/06/27 19:38:16 Running command: terraform apply -lock=true /development/tfplan
Acquiring state lock. This may take a few moments...
Releasing state lock. This may take a few moments...
Acquiring state lock. This may take a few moments...
Plugin reinitialization required. Please run "terraform init".
Reason: Could not satisfy plugin requirements.

Plugins are external binaries that Terraform uses to access and manipulate
resources. The configuration provided requires plugins which can't be located,
don't satisfy the version constraints, or are otherwise incompatible.

2 error(s) occurred:

* provider.aws: no suitable version installed
  version requirements: "1.25"
  versions installed: none
* provider.template: no suitable version installed
  version requirements: "1.0"
  versions installed: none

Terraform automatically discovers provider requirements from your
configuration, including providers used in child modules. To see the
requirements and constraints from each module, run "terraform providers".

Releasing state lock. This may take a few moments...

Error: error satisfying plugin requirements

[terragrunt] 2018/06/27 19:38:31 Hit multiple errors:
exit status 1

Notes / Observations

With the TF_LOG=debug environment variable set the above $ terragrunt apply -lock=true --terragrunt-source /development/infrastructure-modules/ /development/tfplan command includes the below output, that suggests terragrunt apply does not utilize TF_PLUGIN_CACHE_DIR and instead continues to look in .terraform/plugins/

-----------------------------------------------------
2018/06/27 19:32:58 [DEBUG] [aws-sdk-go] {}
2018/06/27 19:32:58 [INFO] command: backend initialized: *s3.Backend
2018/06/27 19:32:58 [DEBUG] checking for provider in "."
2018/06/27 19:32:58 [DEBUG] checking for provider in "/usr/local/Cellar/terraform/0.11.7/bin"
2018/06/27 19:32:58 [DEBUG] checking for provisioner in "."
2018/06/27 19:32:58 [DEBUG] checking for provisioner in "/usr/local/Cellar/terraform/0.11.7/bin"
2018/06/27 19:32:58 [INFO] Failed to read plugin lock file .terraform/plugins/darwin_amd64/lock.json: open .terraform/plugins/darwin_amd64/lock.json: no such file or directory
2018/06/27 19:32:58 [INFO] command: backend *s3.Backend is not enhanced, wrapping in local

--terragrunt-source is used in both plan and apply commands as automation seeks to guarantee the same contents is used by both phases and avoid performance penalties of downloading multiple times.

Although plan-all is used to produce the plan output file, it seems only apply supports a plan input file. Is this correct? A plan input file with apply-all results in:

You can't set variables with the '-var' or '-var-file' flag
when you're applying a plan file. The variables used when
the plan was created will be used. If you wish to use different
variable values, create a new plan file.

I've attempted various scenarios with --terragrunt-non-interactive, -input=false and -auto-approve type flags which have not influence the outcome above.

If I've missed something and there are other approaches for Terragrunt working in an automation / continuous integration world it would be great to have details included in the documentation.

brikis98 commented 6 years ago

There's definitely a bug with using plan files with Terragrunt and extra_arguments: https://github.com/gruntwork-io/terragrunt/issues/493

I don't think we've ever tried plan-all and apply-all with plan files. The provider issue confuses me, as Terragrunt doesn't control where Terraform downloads provider/module code... It just runs terraform init and lets Terraform do its thing...

sjungwirth commented 6 years ago

I recently set up a CI process using terragrunt plan-all and apply-all commands using the plan generated in a previous step.

My setup is:

Also I did notice that I needed to run the apply-all from the same absolute path as I ran the plan-all step from in the previous step.

mholttech commented 6 years ago

I'm running into this same issue when using this type of setup with Atlantis

mholttech commented 6 years ago

@sjungwirth would you be open to sharing your before_hook code?

sjungwirth commented 6 years ago

@mholttech sure, it's nothing too fancy:

#!/bin/bash

parent_dir=$1
relative_path=$2

child_dir="${parent_dir}/${relative_path}";

filename="${child_dir}/create_vars.auto.tfvars";
cat <<EOF > ${filename}
# DO NOT EDIT - File created by bin/create_vars.sh
dept = "${relative_path%/*}"
org_id = "XXXXXX"
billing_account = "XXXXXXXX"
EOF

and the root terragrunt config in /terraform.tfvars:

terragrunt = {
  remote_state {
    backend = "gcs"
    config {
      bucket  = "XXXXXXX"
      prefix    = "${path_relative_to_include()}/terraform.tfstate"
      project = "XXXXXXX"
    }
  }

  terraform {
    before_hook "setup_vars" {
      commands = ["${get_terraform_commands_that_need_vars()}"]
      execute = [
        "${get_parent_tfvars_dir()}/bin/create_vars.sh", "${get_parent_tfvars_dir()}", "${path_relative_to_include()}",
      ]
      run_on_error = false
    }
  }
}

One thing I think I forgot to mention in my previous post, I needed to run terragrunt validate-all ... || true in my CI to initialize the create_vars.auto.tfvars files before trying to do a plan-all. The || true part was to allow my CI to continue even though the validate-all command would return an error code.

mzmudziak commented 4 years ago

@mholttech mholttech I know it's a little bit old issue, but I'm curious if you've ever solved this issue.

I'm unable to run terragrunt plan-all -out=$PLANFILE and then terragrunt apply-all $PLANFILE sequentially, terraform is complaining about unsatisfied plugins.. :(