Create Terraform Cloud Resources

We'd like Terraform to deploy the Terraform Cloud organization, a project within that organization, and a workspace within that project. Further, by linking the GitHub repository with the workspace, we can demonstrate Terraform Cloud's ability to automatically plan and apply changes made by commits to that repository.

To start with, copy and paste the following into main.tf to create the Terraform Cloud Organization and an OAuth client so that Terraform Cloud can watch for and react to commits to GitHub repositories.

resource "tfe_organization" "example" {
  name  = local.tfcloud_org_name
  email = local.tfcloud_org_admin_email

  lifecycle {
    prevent_destroy = true
  }
}

resource "tfe_oauth_client" "example_github" {
  name             = "tfe-tfcloud-mgmt-github-oauth-client"
  organization     = tfe_organization.example.name
  api_url          = "https://api.github.com"
  http_url         = "https://github.com"
  oauth_token      = var.github_ro_token
  service_provider = "github"
}

Next, copy and paste the following into tfcloud_variables.tf. The resources below manage common variable sets that hold the various credentials needed for both Terraform Cloud and the GitHub Terraform provider to interact with their respective APIs:

resource "tfe_variable_set" "tfcloud_common_credentials" {
  organization = tfe_organization.example.id
  name         = "tfcloud-common-credentials"
  description  = "Common credentials required for all tfcloud projects/workspaces"
}

resource "tfe_variable" "tfcloud_common_tfe_token" {
  category        = "terraform"
  key             = "tfe_token"
  sensitive       = true
  description     = "Terraform Enterprise API Token"
  value           = "Manually set to avoid storing in TF state"
  variable_set_id = tfe_variable_set.tfcloud_common_credentials.id

  lifecycle {
    ignore_changes = [value]
  }
}

resource "tfe_variable" "tfcloud_mgmt_gh_ro_token" {
  category        = "terraform"
  key             = "github_ro_token"
  sensitive       = true
  description     = "GitHub PAT for triggering runs"
  value           = "Manually set to avoid storing in TF state"
  variable_set_id = tfe_variable_set.tfcloud_common_credentials.id

  lifecycle {
    ignore_changes = [value]
  }
}

resource "tfe_variable_set" "github_provider_credentials" {
  organization = tfe_organization.example.id
  name         = "github-admin-credentials"
  description  = "Credentials required for the GitHub Terraform provider"
}

resource "tfe_variable" "tfcloud_mgmt_gh_admin_token" {
  category        = "terraform"
  key             = "github_admin_token"
  sensitive       = true
  description     = "GitHub PAT for creating and deleting repositories"
  value           = "Manually set to avoid storing in TF state"
  variable_set_id = tfe_variable_set.github_provider_credentials.id

  lifecycle {
    ignore_changes = [value]
  }
}

Next, copy and paste the following into tfcloud_mgmt_project.tf to create the Terraform Cloud project and workspace along with the associated GitHub repository. This also creates a workspace-scoped "variable set" resource to hold the credentials that Terraform Cloud will need in order to interact with both the Terraform Enterprise API and GitHub API. We follow Hashicorp's recommended practice of scoping the variable sets as narrowly as possible; we don't want any old project or workspace in our organization to be able to make changes to the Terraform Cloud organization.

locals {
  tfcloud_mgmt_project_name = "tfcloud-mgmt"
}

resource "github_repository" "tfcloud_mgmt" {
  name               = local.tfcloud_mgmt_project_name
  auto_init          = true
  gitignore_template = "Terraform"
  license_template   = "mit"

  lifecycle {
    prevent_destroy = true
  }
}

resource "github_branch_default" "tfcloud_mgmt_main" {
  repository = github_repository.tfcloud_mgmt.name
  branch     = "main"
}

resource "github_branch_protection" "tfcloud_mgmt" {
  repository_id  = github_repository.tfcloud_mgmt.name
  pattern        = github_branch_default.tfcloud_mgmt_main.branch
  enforce_admins = true

  required_status_checks {
    strict = false
    contexts = [
      "Terraform Cloud/${tfe_organization.example.name}/${tfe_workspace.tfcloud_mgmt_prod.name}",
    ]
  }
}

resource "tfe_project" "tfcloud_mgmt" {
  organization = tfe_organization.example.id
  name         = local.tfcloud_mgmt_project_name
}

resource "tfe_workspace" "tfcloud_mgmt_prod" {
  name              = "${local.tfcloud_mgmt_project_name}-prod"
  organization      = tfe_organization.example.id
  project_id        = tfe_project.tfcloud_mgmt.id
  terraform_version = "~> 1.6.0"

  tag_names = [
    local.tfcloud_mgmt_project_name,
    "prod"
  ]

  vcs_repo {
    identifier     = github_repository.tfcloud_mgmt.full_name
    oauth_token_id = tfe_oauth_client.example_github.oauth_token_id
  }
}

resource "tfe_workspace_variable_set" "tfcloud_mgmt_prod_tfcloud_common_credentials" {
  workspace_id    = tfe_workspace.tfcloud_mgmt_prod.id
  variable_set_id = tfe_variable_set.tfcloud_common_credentials.id
}

resource "tfe_workspace_variable_set" "tfcloud_mgmt_prod_tfcloud_github_provider_credentials" {
  workspace_id    = tfe_workspace.tfcloud_mgmt_prod.id
  variable_set_id = tfe_variable_set.github_provider_credentials.id
}

Running terraform apply should show that 14 resources need to be created, so go ahead and confirm to get things set up!

$ terraform apply
...
Plan: 14 to add, 0 to change, 0 to destroy.
...
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

Congratulations! You now have a Terraform Cloud organization, project and workspace configured. You also have a GitHub repository that is linked up to that workspace.

Note

Because we initialized a new GitHub repository and then immediately created a workspace linked to it, if you visit the Terraform Cloud UI then you'll notice that a terraform plan has already been triggered and failed because there's no Terraform code in that repository yet. We'll sort that out in just a moment.

Notice that in your current working directory there is a file called terraform.tfstate which holds the state of your Terraform Cloud configuration as far as your local terraform considers it. Alas, Terraform Cloud itself knows nothing of this state of affairs. Next we'll perform a state migration which is how we get your local copy of the state into Terraform Cloud.