Terraform: terraform_remote_state to pass values to other configurations

It would be uncommon to have one monolithic Terraform configuration for all the infrastructure in your organization.  More than likely, there are multiple groups and each has responsibility and ownership of certain components (e.g. networking, storage, authorization, Kubernetes).

As an example, let’s say your responsibility is the Kubernetes cluster build. You may need the following values to complete this task:

  • Network, subnet names, and CIDR blocks created by the Networking team
  • Address and ports to shared storage buckets created by the Storage team

In its most rudimentary form, you could ask these  teams to manually provide these values.  But even better for automation and self-service, you could have terraform remote state pull the outputs from these teams’ terraform state, and use these values to populate your Cluster variables.

Overview

The terraform remote state data source allows you to pull in the state of another Terraform run, and use any of its exposed output variables in your own configuration.  Notice this does not include any of the resources or data of the remote state, only root level output values.

This provides a mechanism for chaining independent Terraform runs, where the output of one configuration can populate the values of the next.

Terraform to create GCP project and bucket

As a prerequisites, you must have: a GCP account, and the terraform and gcloud utilities installed.

Then create a basic GCP project, with the state saved locally as ‘bootstrap.tfstate’.

# get project code
git clone https://github.com/fabianlee/example-terraform_remote_state.git
cd example-terraform_remote_state

# gcloud login for terraform
gcloud auth application-default login

# sets env var 'TF_VAR_prject' with new random GCP project id
source ./sourceMakeRandomProjectId.sh

# creates basic project, saves to local bootstrap.tfstate
cd gcp-project-bootstrap
./init.sh
# shows output: bucketname, mybilling, projid, projname, projnumber, region
./apply.sh
cd ..

# validate local state and variable file
ls -l bootstrap.tfstate
cat dynamic-backend.hcl

The GCS storage bucket created inside the project will be the location of the remote state files in later runs.

Create GCP Topic, using local bootstrap state

Now let’s create a simple GCP Topic using Terraform.  But instead of having to retype or assign values manually for basic values such as project and region, we will instead leverage the local bootstrap state.

Here is the snippet from gcp-topic/provider.tf:

# bootstrap state for base set of values
data "terraform_remote_state" "bootstrapstate" {
  backend = "local"
  config = {
    path = "../bootstrap.tfstate"
  }
}

provider "google" {
      project     = data.terraform_remote_state.bootstrapstate.outputs.projid
      region      = data.terraform_remote_state.bootstrapstate.outputs.region
}

Use gcloud to explicitly login to the new project first, then create this GCP Topic.

# gcloud login for terraform, set to explicit project id
gcloud auth application-default login --project $TF_VAR_project

# creates 'example-topic', saves state to remote GCS
cd gcp-topic
./init.sh
# shows output: topic1_labels, topic1_name
./apply.sh
cd ..

Create another GCP Topic, using remote state of first Topic

Now let’s create another GCP Topic using Terraform.  Once again we will load the local bootstrap state for the basic project values, but we will also load the remote state of the first Topic in order to gets its name and label values.

Here is the snippet from gcp-topic2/provider.tf:

# bootstrap state for base set of values
data "terraform_remote_state" "bootstrapstate" {
  backend = "local"
  config = {
    path = "../bootstrap.tfstate"
  }
}

# remote state from first Topic
data "terraform_remote_state" "topic1" {
  backend = "gcs"
  config = {
    bucket = data.terraform_remote_state.bootstrapstate.outputs.bucketname
    prefix = "gcp-topic"
  }
}

And here is how the values are used to create the second Topic in gcp-topic2/main.tf:

resource "google_pubsub_topic" "example" {
  # same name, but suffixed with '-2'
  name = "${data.terraform_remote_state.topic1.outputs.topic1_name}-2"

  # same labels, but add 'another'
  labels = merge(data.terraform_remote_state.topic1.outputs.topic1_labels,{"another"="value"})
}

Then create this GCP Topic.

# creates 'example-topic-2', based on values from first Topic
cd gcp-topic2
./init.sh
# shows output: topic1_name, topic2_name
./apply.sh
cd ..

Validate GCP Infrastructure

After running the Terraform scripts above you should have two GCP topics, list them with the following gcloud command.

gcloud pubsub topics list --format='value(name,labels)'

Destroy Infrastructure

In order to avoid charges on the GCP infrastructure just created, you should tear down the GCP project.

cd gcp-project-bootstrap
./destroy.sh
cd ..

# remove local state and variables
rm -f bootstrap.tfstate* dynamic-backend.hcl
rm -fr gcp-project-bootstrap/.terraform gcp-project-bootstrap/.terraform.lock.hcl
rm -fr gcp-topic/.terraform gcp-topic/.terraform.lock.hcl
rm -fr gcp-topic2/.terraform gcp-topic2/.terraform.lock.hcl

 

REFERENCES

fabianlee, github project for this article with example usage of terraform_remote_state

hashicorp developer, using refresh-only to update remote state

hashicorp ref, terraform_remote_state

spacelift.io, terraform remote state data source

terraform.io, data source remote_state

terraform.io, resource google_storage_bucket

google ref, use terraform to create GCS bucket

ruanbekker, example amazon s3 remote state

hashicorp developer, using partial backend configuration for dynamic (since variable not possible)

Brendan Thompson, empty backend configuration augmented with backend-config for dynamic backend location

Google ref, topics