|
| 1 | +# Scenario: Agent pool setup |
| 2 | + |
| 3 | +HCP Terraform agents let Terraform runs reach private networks that the hosted |
| 4 | +runners cannot. A working agent setup needs four pieces: |
| 5 | + |
| 6 | +1. An agent pool in the organization. |
| 7 | +2. An agent authentication token for each running agent process. |
| 8 | +3. Workspaces (or projects) configured to use the pool. |
| 9 | +4. One or more agent processes started with the token, pointing at the API |
| 10 | + address. |
| 11 | + |
| 12 | +This scenario covers the SDK side: creating the pool, generating a token, and |
| 13 | +attaching workspaces. Starting the agent process itself is done outside Python |
| 14 | +with the `tfc-agent` binary. |
| 15 | + |
| 16 | +Upstream docs: |
| 17 | + |
| 18 | +- Agents: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agents |
| 19 | +- Agent tokens: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agent-tokens |
| 20 | +- Agent pool concepts: https://developer.hashicorp.com/terraform/cloud-docs/agents |
| 21 | + |
| 22 | +## Prerequisites |
| 23 | + |
| 24 | +```bash |
| 25 | +export TFE_TOKEN="your-api-token" |
| 26 | +export TFE_ADDRESS="https://app.terraform.io" |
| 27 | +``` |
| 28 | + |
| 29 | +The token needs `manage-agent-pools` permission on the organization, and |
| 30 | +workspace write access on any workspaces you intend to attach. |
| 31 | + |
| 32 | +## Step 1: Create the agent pool |
| 33 | + |
| 34 | +```python |
| 35 | +from pytfe import TFEClient |
| 36 | +from pytfe.models import AgentPoolCreateOptions |
| 37 | + |
| 38 | + |
| 39 | +client = TFEClient() |
| 40 | +organization = "my-organization" |
| 41 | + |
| 42 | +pool = client.agent_pools.create( |
| 43 | + organization, |
| 44 | + AgentPoolCreateOptions( |
| 45 | + name="private-network-pool", |
| 46 | + organization_scoped=False, |
| 47 | + allowed_workspace_ids=["ws-abc123"], |
| 48 | + ), |
| 49 | +) |
| 50 | + |
| 51 | +print(pool.id, pool.name) |
| 52 | +``` |
| 53 | + |
| 54 | +Set `organization_scoped=True` to allow every workspace in the organization to |
| 55 | +use the pool. Set it to `False` and pass `allowed_workspace_ids` (and/or |
| 56 | +`allowed_project_ids`) to scope the pool explicitly. Scoped pools are safer for |
| 57 | +shared organizations because they prevent unrelated workspaces from picking up |
| 58 | +the agent. |
| 59 | + |
| 60 | +## Step 2: Create an agent token |
| 61 | + |
| 62 | +Each running agent process needs its own token: |
| 63 | + |
| 64 | +```python |
| 65 | +from pytfe.models import AgentTokenCreateOptions |
| 66 | + |
| 67 | +token = client.agent_tokens.create( |
| 68 | + pool.id, |
| 69 | + AgentTokenCreateOptions(description="agent-host-1"), |
| 70 | +) |
| 71 | + |
| 72 | +print(token.id) |
| 73 | +print(token.token) |
| 74 | +``` |
| 75 | + |
| 76 | +The token value is returned only at creation time. Store it in a secret manager |
| 77 | +immediately and reference it from the agent host's environment. |
| 78 | + |
| 79 | +For multiple agents, create one token per host so revocation is granular: |
| 80 | + |
| 81 | +```python |
| 82 | +for host in ["agent-host-1", "agent-host-2", "agent-host-3"]: |
| 83 | + t = client.agent_tokens.create( |
| 84 | + pool.id, |
| 85 | + AgentTokenCreateOptions(description=host), |
| 86 | + ) |
| 87 | + save_to_secret_manager(host, t.token) |
| 88 | +``` |
| 89 | + |
| 90 | +## Step 3: Attach workspaces to the pool |
| 91 | + |
| 92 | +A workspace uses an agent pool when its `execution_mode` is `agent` and its |
| 93 | +`agent_pool_id` references the pool: |
| 94 | + |
| 95 | +```python |
| 96 | +from pytfe.models import ExecutionMode, WorkspaceUpdateOptions |
| 97 | + |
| 98 | +client.workspaces.update_by_id( |
| 99 | + "ws-abc123", |
| 100 | + WorkspaceUpdateOptions( |
| 101 | + execution_mode=ExecutionMode.AGENT, |
| 102 | + agent_pool_id=pool.id, |
| 103 | + ), |
| 104 | +) |
| 105 | +``` |
| 106 | + |
| 107 | +For a scoped pool, you can also widen or narrow the allowed list later: |
| 108 | + |
| 109 | +```python |
| 110 | +from pytfe.models import AgentPoolAssignToWorkspacesOptions |
| 111 | + |
| 112 | +client.agent_pools.assign_to_workspaces( |
| 113 | + pool.id, |
| 114 | + AgentPoolAssignToWorkspacesOptions( |
| 115 | + workspace_ids=["ws-abc123", "ws-def456"], |
| 116 | + ), |
| 117 | +) |
| 118 | +``` |
| 119 | + |
| 120 | +`assign_to_workspaces` replaces the allowed-workspaces list in full; it does |
| 121 | +not append. Always pass the complete intended list. |
| 122 | + |
| 123 | +## Step 4: Start the agent process |
| 124 | + |
| 125 | +Outside Python, on the host that has the network path to your private |
| 126 | +infrastructure: |
| 127 | + |
| 128 | +```bash |
| 129 | +export TFC_AGENT_TOKEN="<token value from Step 2>" |
| 130 | +export TFC_AGENT_NAME="agent-host-1" |
| 131 | +export TFC_ADDRESS="https://app.terraform.io" |
| 132 | + |
| 133 | +tfc-agent |
| 134 | +``` |
| 135 | + |
| 136 | +Confirm the agent registered: |
| 137 | + |
| 138 | +```python |
| 139 | +for agent in client.agents.list(pool.id): |
| 140 | + print(agent.id, agent.name, agent.status) |
| 141 | +``` |
| 142 | + |
| 143 | +A healthy agent reports `status="idle"` or `status="busy"`. |
| 144 | + |
| 145 | +## Cleanup |
| 146 | + |
| 147 | +Revoke tokens when a host is decommissioned. Delete the pool only after no |
| 148 | +workspaces or projects reference it. |
| 149 | + |
| 150 | +```python |
| 151 | +client.agent_tokens.delete(token.id) |
| 152 | + |
| 153 | +# Detach the workspace before deleting the pool. |
| 154 | +client.workspaces.update_by_id( |
| 155 | + "ws-abc123", |
| 156 | + WorkspaceUpdateOptions(execution_mode=ExecutionMode.REMOTE), |
| 157 | +) |
| 158 | +client.agent_pools.delete(pool.id) |
| 159 | +``` |
| 160 | + |
| 161 | +## Operational notes |
| 162 | + |
| 163 | +- Treat agent tokens like SSH keys: one per host, rotated, stored in a secret |
| 164 | + manager. |
| 165 | +- Prefer scoped pools over organization-scoped pools when only some workspaces |
| 166 | + need private-network access. |
| 167 | +- Agent processes hold long-lived connections. Restart them after rotating |
| 168 | + tokens or upgrading the agent binary. |
| 169 | +- An agent pool with no running agents will leave runs queued indefinitely. |
| 170 | + Monitor `client.agents.list(pool.id)` from your observability stack. |
0 commit comments