2
votes

I am attempting to setup a terraform project, using remote storage (Terraform Cloud), that will primarily provision Google Cloud Platform resources. As part of the infrastructure I require 3 environments that will be managed using terraform workspaces. Each environment will have it's own directory in my repository, within each I'll define the environment specific resources. The directory structure looks similar to:

|- terraform-project
   |- environments
   |  |- staging
   |  |  |- main.tf
   |  |  |- outputs.tf
   |  |  |- variables.tf
   |  |- production
   |     |- main.tf
   |     |- outputs.tf
   |     |- variables.tf
   |- backend.tf
   |- main.tf
   |- outputs.tf
   |- variables.tf

However each environment needs to use the same Google Cloud Platform project. I would typically create the project using the following inside the root-most main.tf file:

resource "random_id" "project_id" {
  byte_length = 4
  prefix      = "${var.project_name}-"
}

resource "google_project" "project" {
  name            = var.project_name
  project_id      = random_id.project_id.hex
  billing_account = var.billing_account
  org_id          = var.org_id
}

So my question is how would I create the project only the once and share this between environments? Doing the following inside each environment main.tf does not work:

resource "google_compute_network" "vpc_network" {
  name    = "staging-network"
  project = google_project.project.project_id
}

The google_project.project.project_id resource cannot be found. Presumably because the terraform plan environments/{staging,production} command does not know to look up the directory tree.

I thought about using a module but given the code above uses a random id would this not cause the project to be created once for each environment, but with a different id?


Edit: Another idea is to create a core workspace that will contain the setup of the Google Cloud Platform project, and any other shared resources. Then each environment will include a data block pointing to the remote state of the core workspace:

data "terraform_remote_state" "core" {
  backend = "remote"
  ...
}

resource "google_compute_network" "vpc_network" {
  name    = "staging-network"
  project = data.terraform_remote_state.core.outputs.project_id
}

Is this an acceptable solution?

2

2 Answers

1
votes

The idea I went for in the end was to create another workspace to hold the shared, "core" infrastructure pieces. This is called core and its sole purpose (at least for now) is to create the Google Cloud Platform project. It then outputs the project id to be used by other workspaces. The final directory structure looked like this:

|- terraform-project
   |- environments
   |  |- core
   |  |  |- main.tf
   |  |  |- outputs.tf
   |  |  |- variables.tf
   |  |- staging
   |  |  |- main.tf
   |  |  |- outputs.tf
   |  |- production
   |     |- main.tf
   |     |- outputs.tf
   |- backend.tf

where environments/core/main.tf has the following configuration:

provider "google" {
  project = "admin-project"
  version = "~> 3.6.0"
}

resource "random_id" "project_id" {
  byte_length = 4
  prefix      = "${var.project_name}-"
}

resource "google_project" "project" {
  name            = var.project_name
  project_id      = random_id.project_id.hex
  billing_account = var.billing_account
  org_id          = var.org_id
}

and each of the other environment main.tf files have the following configuration:

provider "google" {
  project = "admin-project"
  version = "~> 3.6.0"
}

data "terraform_remote_state" "core" {
  backend = "remote"

  config = {
    organization = "my-org"
    workspaces = {
      name = "networking-core"
    }
  }
}

resource "google_compute_network" "vpc_network" {
  name        = "my-network"
  project     = terraform_remote_state.core.outputs.project_id
}

This of course now creates a dependancy between the workspaces that I use but I don't view this necessarily as a disadvantage.

0
votes

Use the following Project directory

| - Terraform Project
  |- staging
    |- main.tf
    |- backend.tf
    |- provider.tf
    |- vars.tf
    |- output.tf
  |- production
    |- main.tf
    |- backend.tf
    |- provider.tf
    |- vars.tf
    |- output.tf
  |- modules
    |- main.tf
    |- vars.tf
    |- output.tf

Main Business logic will be in the modules/main.tf file.

While provisioning the Resources per Environment, your {{env_type}}/main.tf will call the module, for example:-

staging/main.tf:-

module "example" {
  source = "../modules/"
}

staging/provider.tf:-

provider "google" {
  project     = var.PROJECT_NAME
  credentials = "xxxxxxxxxxx"
  region      = "${var.REGION}"
}

Same will be for Production Environment type, So this you can use the same code and project for multiple environment type example