hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
41.68k stars 9.41k forks source link

Read Terraform Backend Config or allow init level variables #31993

Open WilliamABradley opened 1 year ago

WilliamABradley commented 1 year ago

Terraform Version

Terraform v1.3.2
on windows_amd64

Use Cases

The idea behind this is the ability to change how Terraform names things based on the environment.

Currently, you can do this but you need to ask a variable for the environment when you plan/apply, but this could be inferred from the backend's AWS Profile, as an example.

Attempted Solutions

It is doable, but you have to set the environment separately, when a bunch of this can be inferred.

(Those backend configs have been inlined, we store them in ./staging.hcl for ease of use)

terraform init --backend-config="profile=rv-staging" --backend-config="bucket=rv-terraform-state-staging"
terraform {
  required_version = ">= 1.1.0, < 2.0.0"

  backend "s3" {
    region = "ap-southeast-2"
    key    = "infrastructure.tfstate"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
    }
  }
}

variable "AWS_REGION" {
  type    = string
  default = "ap-southeast-2"
}

variable "AWS_PROFILE" {
  type    = string
  default = null
}

variable "ENVIRONMENT" {
  type = string
}

locals {
  workspace_profiles = {
    staging    = "rv-staging"
    production = "rv-production"
  }
  profile = var.AWS_PROFILE != null ? var.AWS_PROFILE : local.workspace_profiles[var.ENVIRONMENT]
}

provider "aws" {
  region  = var.AWS_REGION
  profile = local.profile
}

Proposal

These proposals ease the effort of plans/applies, at the cost of some magic 🪄 However, a side benefit is that you can guarantee that you don't mismatch the state file and the ENVIRONMENT variable.

terraform init --backend-config="profile=rv-staging" --backend-config="bucket=rv-terraform-state-staging"
terraform {
  required_version = ">= 1.1.0, < 2.0.0"

  backend "s3" {
    region = "ap-southeast-2"
    key    = "infrastructure.tfstate"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
    }
  }
}

locals {
  # assumes format is `rv-{environment}`
  environment = split("-", terraform.backend.s3.profile)[1]
}

provider "aws" {
  region  = terraform.backend.s3.region
  profile = terraform.backend.s3.profile
}

Alternative:

terraform init --backend-config="profile=rv-staging" --backend-config="bucket=rv-terraform-state-staging"  --backend-config="vars.environment=staging" --backend-config="vars.profile=rv-staging" --backend-config="vars.region=ap-southeast-2"
terraform {
  required_version = ">= 1.1.0, < 2.0.0"

  backend "s3" {
    region = "ap-southeast-2"
    key    = "infrastructure.tfstate"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
    }
  }
}

locals {
  environment = terraform.backend.vars.environment
}

provider "aws" {
  region  = terraform.backend.vars.region
  profile = terraform.backend.vars.profile
}

References

No response

crw commented 1 year ago

Thanks for this feature request!

onitake commented 1 year ago

I wonder why this isn't already possible. :thinking:

The counterpart to this feature (using regular variables in backend configs) is obviously much more complicated to implement, but adding a namespace for reading out backend configurations shouldn't be that hard to implement? I mean, Terraform is already referencing the backend when planning or applying, so why not expose it to scripts?

One very useful example would be creating IAM policies based on the backend config:

terraform {
  backend "s3" {
    # backend config is in backend.tfvars
  }
}
data "aws_iam_policy_document" "tfstate" {
  statement {
    actions = ["s3:ListBucket"]
    resources = [backend.s3.config.bucket]
  }
  statement {
    actions = [
      "s3:GetObject",
      "s3:PutObject",
      "s3:DeleteObject",
    ]
    resources = [
      "${backend.s3.config.bucket}/${backend.s3.config.key}",
    ]
  }
}