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
8.04k stars 975 forks source link

Dependency fails to create Terraform dot directory and state file #1416

Closed franz-josef-kaiser closed 3 years ago

franz-josef-kaiser commented 3 years ago

Task:

Version remote state by changing location.

  1. Use GitLab managed Terraform State as the Terraform state storage backend.
  2. Change remote state location when Git tag is incremented:

    https://gitlab.com/api/v4/projects/<PROJECT_ID>/terraform/state/<ENV>-<GIT_TAG>

Attempt:

Use external data source.

  1. Use external data source to run git command.
  2. Present data as output.
  3. Utilize Terragrunt dependencies to set execution order.
  4. Utilize Terraform dependency to extract the data and load it into inputs for usage.

The directory structure is the following:

$ tree
.
├── modules
│   └── version
│       ├── bin
│       │   └── version.sh
│       ├── main.tf
│       └── terragrunt.hcl
├── prod
│   └── main
│       └── terragrunt.hcl
└── terragrunt.hcl

The root terragrunt.hcl has the following contents (locals omitted) to build the backends config data like the remote state backend address:


dependency "data" {
  config_path = "${get_parent_terragrunt_dir()}/modules/version"
}

dependencies {
  paths = [ "${get_parent_terragrunt_dir()}/modules/version" ]
}

remote_state {
  backend = "http"
  config = {
    address        = "https://gitlab.com/api/v4/projects/${local.project_id}/terraform/state/${local.env}-${dependency.data.outputs.tag.version}"
    lock_address   = "https://gitlab.com/api/v4/projects/${local.project_id}/terraform/state/${local.env}-${dependency.data.outputs.tag.version}/lock"
    unlock_address = "https://gitlab.com/api/v4/projects/${local.project_id}/terraform/state/${local.env}-${dependency.data.outputs.tag.version}/lock"
    username       = local.username
    password       = local.token
    lock_method    = "POST"
    unlock_method  = "DELETE"
    retry_wait_min = 5
  }
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
}

inputs = {
  version = dependency.data.outputs.tag
}

The version module is simply a call to a shell script and an output to make data available to the root backend:config.

data "external" "this" {
  program = [
    "sh",
    "./bin/version.sh"
  ]
}
output "tag" {
  value = data.external.this.result
}
(open shell script) The shell script itself is simply a call to `git`, wrapped in a call to `jq` to transform the output to valid JSON.

```python #!/usr/bin/env bash set -eo pipefail VERSION=$(git describe --tags --abbrev=0 | sed 's/\.//g') jq -n \ --arg version "${VERSION}" \ '{"version":$version}' ```

Problem

Fetching the Git tag and providing it to Terraform or Terragrunt seems to fail with output not being created up front.

(open full log) Running `terragrunt plan` runs `terraform init` in the `module/version` (dependency) folder and creates the `.terraform-cache/` dir. It then fails because of a missing `output`. The `terraform.tfstate` file has **not been created**. ```python [terragrunt] 2020/11/04 12:32:50 /path/to/root/modules/version/terragrunt.hcl is a dependency of /path/to/root/prod/main/terragrunt.hcl but detected no outputs. Either the target module has not been applied yet, or the module has no outputs. If this is expected, set the skip_outputs flag to true on the dependency block. ```

### Log ```python $ terragrunt plan -out plan.cache [INFO] Getting version from tgenv-version-name [INFO] TGENV_VERSION is 0.25.5 [terragrunt] [/path/to/root/prod/main] 2020/11/04 12:32:03 Running command: terraform --version [terragrunt] 2020/11/04 12:32:07 Terraform version: 0.13.5 [terragrunt] 2020/11/04 12:32:07 Reading Terragrunt config file at /path/to/root/prod/main/terragrunt.hcl [terragrunt] 2020/11/04 12:32:07 WARNING: Could not parse remote_state block from target config /path/to/root/modules/version/terragrunt.hcl [terragrunt] 2020/11/04 12:32:07 WARNING: Falling back to terragrunt output. [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:07 Running command: terraform --version [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:11 Terraform version: 0.13.5 [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:11 Reading Terragrunt config file at /path/to/root/modules/version/terragrunt.hcl [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:11 Downloading Terraform configurations from file:///path/to/root/modules/version into /path/to/root/modules/version/.terragrunt-cache/ly5VMHkzUWbonYSR1gKb-vAc8h0/cmWUxX-13YOmtGNQ8iWOkHKAA6Y [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:11 Copying files from /path/to/root/modules/version into /path/to/root/modules/version/.terragrunt-cache/ly5VMHkzUWbonYSR1gKb-vAc8h0/cmWUxX-13YOmtGNQ8iWOkHKAA6Y [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:11 Setting working directory to /path/to/root/modules/version/.terragrunt-cache/ly5VMHkzUWbonYSR1gKb-vAc8h0/cmWUxX-13YOmtGNQ8iWOkHKAA6Y [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:11 Running command: terraform init Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/external... - Installing hashicorp/external v2.0.0... - Installed hashicorp/external v2.0.0 (signed by HashiCorp) The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, we recommend adding version constraints in a required_providers block in your configuration, with the constraint strings suggested below. * hashicorp/external: version = "~> 2.0.0" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. ####### FAILS HERE ######## [terragrunt] [/path/to/root/modules/version] 2020/11/04 12:32:49 Running command: terraform output -json [terragrunt] 2020/11/04 12:32:50 /path/to/root/modules/version/terragrunt.hcl is a dependency of /path/to/root/prod/main/terragrunt.hcl but detected no outputs. Either the target module has not been applied yet, or the module has no outputs. If this is expected, set the skip_outputs flag to true on the dependency block. [terragrunt] 2020/11/04 12:32:50 Unable to determine underlying exit code, so Terragrunt will exit with error code 1 ``` …

Switching to the modules/version/ dir manually and calling terragrunt init does not change this. Still there is no terraform.tfstate file that holds the output. And neither is there a .terraform dir.

The .terraform dir will only get created when calling terraform init inside the modules/version/ dir manually.

So after running terraform init; terraform refresh in the modules/version/ dir manually, creates the output. It can then get used by the root terragrunt.hcl to construct the remote_state address, etc.

I tried using a before_hook to trigger the generation, but nothing runs before init.

There's also the thing, that this would need a manual refresh after each single Git tag generation.

In the end, I used the Bash script and let it simply run with

address = "[…]${run_cmd("${get_parent_terragrunt_dir()}/bin/version.sh", "")}"

I just have to make sure to echo -n the $VERSION instead of outputting JSON. (Note: -n to avoid new lines at the end`).

Questions:

  1. Am I doing something wrong with the external data source as dependency? Could this be solved easier this way and without needing the manual initialization and refreshing?
  2. Any chance we can use local exec here?
  3. Any chance to run anything pre init?
  4. Is there a better way than just stuffing this as inputs/ Env vars inside the terragrunt.hcl file?
brikis98 commented 3 years ago

Did you ever run apply on the version module? If you didn't, then no state file will exist, and calling terraform output will fail with the error shown.

brikis98 commented 3 years ago

Also, you can simplify things by using run_cmd to run your script, instead of a separate module.

franz-josef-kaiser commented 3 years ago

@brikis98 I used run_cmd in the end, as written above.

Out of curiosity: Can you imagine a way to make this easier than running the dependency as if it would be a completely separate project with separate state, etc.? For e.g. with a local-exec provisioner? I could not get this to work. Thanks in advance!

brikis98 commented 3 years ago

If run_cmd works, why do you need dependency?

franz-josef-kaiser commented 3 years ago

I don't. I am just curious and want to learn.

yorinasub17 commented 3 years ago

Closing this as the original question has been answered. If there are any follow ups, please open a new ticket with updated context. Thanks!