I’ve used a module that Grant authored in the early days. Its goal is to create a Terraform Workspace and a corresponding GitHub repo. This would then link the two so any Terraform code committed to this folder would trigger a webhook into Terraform Cloud. This allows me to use Terraform to automate Terraform provisioning/standup. With the introduction of Terraform Agents there is a new execution mode. This execution mode, Agent, allows a workspace to pass the run to a remote agent!

This however broke the module. I would manually convert a workspace and Terraform would throw up this on any subsequent runs.

Error: agent_pool_id must be provided when execution_mode is 'agent'

  on .terraform/modules/tf-vsphere-module/main.tf line 5, in resource "tfe_workspace" "this":
   5: resource tfe_workspace "this" {

Time to fix

It is time to adjust the module to accommodate those that workspaces that wanted to use Agents. So before I put code to IDE, I needed to assess the logic.

  • Boolean on Agent - True or False
  • Reuse defined Agent pool and not a new pool per workspace
  • Ensure that I could enable this per workspace

With that - lets code.

It took E-ternary for me to operate this

ternary operators here are what helped me. I admit, as someone who uses Terraform as a means to an end, I am in no way proficient. This however was something where I could use some logic. Here is the GitHub Gist to follow along with.

Note on line 6 we have execution_mode = var.execution_mode which will read from the defined execution mode variable. In variables.tf of the module we have

variable "execution_mode" {
  type = string
  default = "remote"
  
}

This will ensure the value of var.execution_mode is default. If I define in my module execution_mode = true I override this. I then want to trigger the agent pool.

This allows me to refer to the data source of the tfe_agent_pool by name of laptop within the TFC organisation burkey. On the GitHub Gist on line 8 this is where it all comes together. Line 8 is below of the module

agent_pool_id = var.execution_mode == "agent" ? var.agent_pool_id : null

The logic of this value is as follows:

  • If the value of var.execution_mode is EQUAL_TO “agent”
  • THEN agent_pool_id = var.agent.pool_id
  • ELSE (:) the value of agent_pool_id is null

This allows me to have some conditional logic which means I can use the module code for workspaces with or without the agent configured. Why is this important?

The documentation for Agents states:

agent_pool_id - (Optional) The ID of an agent pool to assign to the workspace. Requires execution_mode to be set to agent. This value must not be provided if execution_mode is set to any other value or if operations is provided.

Essentially the logic will ensure we don’t surface an error with an execution_mode of remote and having agent_pool_idhaving a value!

Consuming this

Now I want to pre-define an existing Agent Pool. This is due to me having the laptop agent pool which pins to an existing Agent on my Laptop. This is defined where I consume my module.

In my terraform_workspace repo where I write my code I have this block.

data "tfe_agent_pool" "laptop_agent_pool" {
  name          = "laptop"
  organization  = "burkey"
}

My module being consumed:

module "tf-devstack" {
  source = "app.terraform.io/burkey/workspace/tfe"
  version = "0.0.24" 
  providers = {
    github = github.personal
  }
  repository_name = "tf-devstack"
  create_repo = true
  repository_private = false
  oauth_token_id = var.oauth_token_id
  execution_mode = "agent"
  agent_pool_id = data.tfe_agent_pool.laptop_agent_pool.id
  tf_var = merge({
    "ssh_public_key" = {
      "value" = local.ssh_public_key,
      "sensitive" = false
    }
  },
  var.slack_webhook)
}

Note now I have the ability to define agent_pool_id and execution_mode. Other instances of this module are on version 0.0.23 which doesn’t have this feature. Here’s an example of module version iteration which I can talk about another time.

Additional Thoughts

  • If someone wanted to have numerous agent pools defined it could be possible for the agent_pool ID field to be read from another workspace with remote state
  • If someone wanted to minimise inputs the logic in the module could be if agent_pool_id is specified then set execution mode to agent. This would improve consumption experience.

Summary

The takeaway from this is that modules allow for repeatable workflows that abstraction extraneous code away from the consumer. They also can benefit from ternary operator logic or abstract various conditional scenarios away. In addition - it makes for easier code once you grok it!

Kudos for Grant for the sanity check and the initial boiler template.