Terraform workspace states in different s3 buckets? - terraform

I use terraform to provision resources in dev and prod environments. These environments live on two different aws accounts. I had my state locally but I want to push it to s3 now. The problem is that terraform stores the state for the def and prod envs in the same s3 bucket is it possible to separate them? If not what are some alternative solutions without splitting my terraform codebase?

I have a bash wrapper around terraform and create a state file per account for separation of concerns. I also break the automation into many components to keep the state small so that performance does not suffer and downloading and uploading the state to the bucket :
function set_backend () {
local STATE_PATH=$1
if [[ $BACKEND == "s3" ]]; then
cat << EOF > ./backend.tf
terraform {
backend "s3" {
bucket = "${TF_VAR_state_bucket}"
dynamodb_table = "${DYNAMODB_STATE_TABLE}"
key = "terraform/$STATE_PATH/terraform.tfstate"
region = "$REGION"
encrypt = "true"
}
}
provider "aws" {
region = "$REGION"
version = "1.51.0"
}
provider "aws" {
region = "$DR_REGION"
version = "1.51.0"
alias = "dr"
}
provider "archive" { version = "1.1.0" }
provider "external" { version = "1.0.0" }
provider "local" { version = "1.1.0" }
provider "null" { version = "1.0.0" }
provider "random" { version = "2.0.0" }
provider "template" { version = "1.0.0" }
provider "tls" { version = "1.2.0" }
EOF
fi
}

Terragrunt is a great tool to use for managing terraform state files for different environments and to store state files in different buckets, instead of to use terraform workspace.
Useful links,
https://transcend.io/blog/why-we-use-terragrunt
https://blog.gruntwork.io/how-to-manage-terraform-state-28f5697e68fa

Related

terraform remote state best practice

I am creating a few terraform modules and inside the modules I also create the resources for storing remote state ( a S3 bucket and dynamodb table)
when I then use the module I launch I write something like this:
# terraform {
# backend "s3" {
# bucket = "name"
# key = "xxxx.tfstate"
# region = "rrrr"
# encrypt = true
# dynamodb_table = "trrrrr"
# }
# }
terraform {
required_version = ">= 1.0.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = var.region
}
module "mymodule" {
source = "./module/mymodule"
region = "param1"
prefix = "param2"
project = "xxxx"
username = "ddd"
contact = "myemail"
table_name = "table-name"
bucket_name = "uniquebucketname"
}
where I leave commented out the part on remote state and I leave terraform to create a local state and create all resources (including the bucket and the DynamoDB table).
After the resources are created
I re-run terraform init and I migrate the state to s3.
I wonder if this is a good practice or if there is something better for maintaining the state and also provide isolation.
That is an interesting approach. I would create the S3 bucket manually since it's a 1 time create for your state file mgmt. Then I would add a policy to prevent deletion | see here: https://serverfault.com/questions/226700/how-do-i-prevent-deletion-of-s3-buckets | & versioning and/or a bkp.
Beyond this approach there are better practises such as using tools like Terraform Cloud which is free for 5 users. Then in your terraform root module configuration you would put this:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "YOUR-TERRAFORM-CLOUD-ORG"
workspaces {
# name = "" ## For single workspace jobs
# prefix = "" ## for multiple workspaces
name = "YOUR-ROOT-MODULE-WORKSPACE-NAME"
}
}
}
More details in this similar Q&A: Initial setup of terraform backend using terraform

Using multiple provider version in terraform

Im trying to use two provider version with in terraform, though Im getting the below error
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider hashicorp/aws:
no available releases match the given constraints >= 3.71.0, 3.71.0, 4.6.0
Here is what Im trying to do. I have a terraform file, which uses multiple module. And in one module alone, I need to use aws provider version 4.6.0. On other modules, I need to stick to currently applied provider version, which is 3.71.0
Terraform version: 0.13.6
Im defining a constraint in terraform file, so "hashicorp/aws" can be anything above 3.71.0. Below is what is defined:
"aws": {
"version": ">= 3.71.0",
"assume_role": {
"role_arn": "....",
"session_name": "..."
},
terraform file calls more than 10 module, and module 0 to 9 provider config is
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "= 3.71.0"
}
}
}
and 10th module provider config is
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "= 4.6.0"
}
}
}
Anything Im missing?
Note : I have already referred this post - Multiple provider versions with Terraform, though not sure, if its technically not possible, and something Im doing it wrong
We use a separate provider block for each and every region, this is how we handle it:
provider "aws" {
region = "us-west-2"
}
provider "aws" {
alias = "east-2-provider"
region = "us-east-2"
version = "~> 4.0"
}
provider "aws" {
alias = "east-1-provider"
region = "us-east-1"
version = "~> 3.74"
}
When we are using a module we use it as below:
module "example-1" {
source = "./example"
providers = {
aws = east-1-provider
}
}
module "example-2" {
source = "./example"
providers = {
aws = east-2-provider
}
}

connect 2 terraform remote states

I'm working on a terraform task, where I need to connect two terraform s3 backends. We have a 2 repos for our tf script. The main one is for creating dev/qa/prod envs and the other one is for managing users/policies required for the first script.
We use s3 as the backend and I want to connect both the backend together so they can take ids/names from each other with out hardcoding them.
Say you have a backend A / terraform project A with your ids/names:
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
}
}
output "names" {
value = [ "bob", "jim" ]
}
In your other terraform project B you can refer to the above backend A as a data source:
data "terraform_remote_state" "remote_state" {
backend = "s3"
config = {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
}
}
Then in the terraform project B you can fetch the outputs of the remote state with names/ids:
data.terraform_remote_state.remote_state.outputs.names

terraform_remote_state data block syntax

I'm working on an AWS multi-account setup with Terraform. I've got a master account that creates several sub-accounts, and in the sub-accounts I'm referencing the master's remote state to retrieve output values.
The terraform plan command is failing for this configuration in a test main.tf:
terraform {
required_version = ">= 0.12.0"
backend "s3" {
bucket = "bucketname"
key = "statekey.tfstate"
region = "us-east-1"
}
}
provider "aws" {
region = "us-east-1"
version = "~> 2.7"
}
data "aws_region" "current" {}
data "terraform_remote_state" "common" {
backend = "s3"
config {
bucket = "anotherbucket"
key = "master.tfstate"
}
}
With the following error:
➜ test terraform plan
Error: Unsupported block type
on main.tf line 20, in data "terraform_remote_state" "common":
20: config {
Blocks of type "config" are not expected here. Did you mean to define argument
"config"? If so, use the equals sign to assign it a value.
From what I can tell from the documentation, this should be working… what am I doing wrong?
➜ test terraform -v
Terraform v0.12.2
+ provider.aws v2.14.0
Seems the related document isn't updated after upgrade to 0.12.x
As the error prompt, add = after config
data "terraform_remote_state" "common" {
backend = "s3"
config = {
bucket = "anotherbucket"
key = "master.tfstate"
}
}
If the problem is fixed, recommend to raise a PR to update the document, then others can avoid the same issue again.

Terraform s3 backend vs terraform_remote_state

According to the documentation, to use s3 and not a local terraform.tfstate file for state storage, one should configure a backend more or less as follows:
terraform {
backend "s3" {
bucket = "my-bucket-name"
key = "my-key-name"
region = "my-region"
}
}
I was
using a local (terraform.tfstate) file
added the above snippet in my provided.tf file
run (again) terraform init
was asked by terraform to migrate my state to the above bucket
...so far so good...
But then comes this confusing part about terraform_remote_state ...
Why do I need this?
Isn't my state now saved remotely (on the aforemenetioned s3 bucket) already?
terraform_remote_state isn't for storage of your state its for retrieval in another terraform plan if you have outputs. It is a data source. For example if you output your Elastic IP Address in one state:
resource "aws_eip" "default" {
vpc = true
}
output "eip_id" {
value = "${aws_eip.default.id}"
}
Then wanted to retrieve that in another state:
data "terraform_remote_state" "remote" {
backend = "s3"
config {
bucket = "my-bucket-name"
key = "my-key-name"
region = "my-region"
}
}
resource "aws_instance" "foo" {
...
}
resource "aws_eip_association" "eip_assoc" {
instance_id = "${aws_instance.foo.id}"
allocation_id = "${data.terraform_remote_state.remote.eip_id}"
}
edit: If you are retrieving outputs in Terraform > 0.12 you need to include outputs
data "terraform_remote_state" "remote" {
backend = "s3"
config {
bucket = "my-bucket-name"
key = "my-key-name"
region = "my-region"
}
}
resource "aws_instance" "foo" {
...
}
resource "aws_eip_association" "eip_assoc" {
instance_id = "${aws_instance.foo.id}"
allocation_id = "${data.terraform_remote_state.remote.outputs.eip_id}"
}
Remote State allows you to collaborate with other team members, and central location to store your infrastructure state.
Apart from that by enabling s3 versioning, you can have versioning for state file, to track changes.

Resources