Search code examples
amazon-web-servicesterraformterraform-provider-awsterraform-aws-modules

How can I reference a value from a different module?


I want to deploy an RDS database to AWS with a secret from AWS Secrets Manager. I have:

├─ environments
│   └─ myenv
│       ├── main.tf
│       ├── locals.tf
│       └── variables.tf
└─ modules
    ├─ db
    │   ├── main.tf
    │   └── variables.tf
    └─ secrets
        └── main.tf

In myenv/main.tf I define a module mydb that has modules/db/main.tf as source where a resource database is defined. Save for the password it all works, I specify values in blocks in myenv and the values "trickle down". But for the credentials, I don't want to hard code them in myenv of course. Instead in modules/secrets I define

data "aws_secretsmanager_secret_version" "my_credentials" {
  # Fill in the name you gave to your secret
  secret_id = "my-secret-id"
}

and with another block:

locals {
  decoded_secrets = jsondecode(data.aws_secretsmanager_secret_version.my_credentials.secret_string)
}

I decode the secrcets and now I want to reference them as e.g. local.decoded_secrets.username in myenv/main. That is my interpretation of the tutorials. But it doesn't work: If I put the locals block in myenv it cannot reference data, and when I put it in modules/secrets then myenv cannot reference locals. How can I combine the values of these two modules in my myenv/main?


Solution

  • You have a few options available to you here to pass the secret to the database module.

    The smallest thing you need to do from your existing setup would be to call both modules at the same time and pass an output from the secrets module to the database module like this:

    .
    ├── environments
    │   └── myenv
    │       ├── locals.tf
    │       ├── main.tf
    │       └── variables.tf
    └── modules
        ├── db
        │   ├── main.tf
        │   └── variables.tf
        └── secrets
            ├── main.tf
            └── outputs.tf
    

    modules/secrets/outputs.tf

    output "secret_id" {
      value = aws_secretsmanager_secret_version.secret.secret_id
    }
    

    environments/myenv/main.tf

    module "secrets" {
      source = "../../modules/secrets"
      # ...
    }
    
    module "db" {
      source    = "../../modules/db"
      # ...
      secret_id = module.secrets.secret_id
    }
    

    A better approach however might be to have the database module create and manage its own secret and not require the secret to be passed into the database module as a parameter at all. If you wanted to reuse the secrets module with other modules then you could make it a child module of the database module or if this is the only place you currently use the secrets module then unnesting these makes things simpler.

    Nesting modules

    modules/db/main.tf

    module "database_password_secret_id" {
      source = "../secrets"
      # ...
    }
    
    data "aws_secretsmanager_secret_version" "database_password" {
      secret_id = module.database_password_secret_id.secret_id
    }
    

    Unnesting the modules

    .
    ├── environments
    │   └── myenv
    │       ├── locals.tf
    │       ├── main.tf
    │       └── variables.tf
    └── modules
        └── db
            ├── main.tf
            ├── secrets.tf
            └── variables.tf
    

    modules/db/secrets.tf

    resource "aws_secretsmanager_secret" "database_password" {
      name = "database-password"
    }
    
    resource "random_password" "database_password" {
      length = 32
    }
    
    resource "aws_secretsmanager_secret_version" "database_password" {
      secret_id     = aws_secretsmanager_secret.example.id
      secret_string = random_password.database_password.result
    }
    

    modules/db/main.tf

    resource "aws_db_instance" "database" {
      # ...
      password = aws_secretsmanager_secret_version.database_password.secret_string
    }