|
| 1 | +# Stacks |
| 2 | + |
| 3 | +HCP Terraform Stacks let you manage multiple Terraform components as a single |
| 4 | +unit, with coordinated deployments across multiple environments. The pytfe SDK |
| 5 | +covers the full lifecycle: creating stacks, preparing configurations, |
| 6 | +orchestrating deployment groups and runs, inspecting deployment steps, reading |
| 7 | +stack states, and handling diagnostics. |
| 8 | + |
| 9 | +Upstream docs: |
| 10 | + |
| 11 | +- Stacks: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stacks |
| 12 | +- Stack configurations: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-configurations |
| 13 | +- Stack deployments: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-deployments |
| 14 | +- Stack deployment groups: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-deployment-groups |
| 15 | +- Stack deployment runs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-deployment-runs |
| 16 | +- Stack deployment steps: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-deployment-steps |
| 17 | +- Stack states: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-states |
| 18 | +- Stack diagnostics: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/stacks/stack-diagnostics |
| 19 | + |
| 20 | +Examples: |
| 21 | + |
| 22 | +- [stack.py](../../examples/stack.py) |
| 23 | +- [stack_configuration.py](../../examples/stack_configuration.py) |
| 24 | +- [stack_deployment.py](../../examples/stack_deployment.py) |
| 25 | +- [stack_deployment_group.py](../../examples/stack_deployment_group.py) |
| 26 | +- [stack_deployment_run.py](../../examples/stack_deployment_run.py) |
| 27 | +- [stack_deployment_step.py](../../examples/stack_deployment_step.py) |
| 28 | +- [stack_state.py](../../examples/stack_state.py) |
| 29 | +- [stack_configuration_summary.py](../../examples/stack_configuration_summary.py) |
| 30 | +- [stack_deployment_group_summary.py](../../examples/stack_deployment_group_summary.py) |
| 31 | +- [stack_diagnostic.py](../../examples/stack_diagnostic.py) |
| 32 | + |
| 33 | +See the end-to-end scenario at [stack-deployment.md](../scenarios/stack-deployment.md). |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Stacks (`client.stacks`) |
| 38 | + |
| 39 | +| Method | Purpose | |
| 40 | +|---|---| |
| 41 | +| `client.stacks.create(options)` | Create a stack in a project. | |
| 42 | +| `client.stacks.update(stack_id, options)` | Update a stack's name, description, or VCS settings. | |
| 43 | +| `client.stacks.list(organization, options)` | Iterate stacks in an organization. | |
| 44 | +| `client.stacks.read(stack_id)` | Read a single stack. | |
| 45 | +| `client.stacks.delete(stack_id)` | Delete a stack. | |
| 46 | +| `client.stacks.force_delete(stack_id)` | Force-delete a stack that cannot be deleted normally. | |
| 47 | + |
| 48 | +```python |
| 49 | +from pytfe import TFEClient |
| 50 | +from pytfe.models import Project, StackCreateOptions, StackListOptions, VCSRepo |
| 51 | + |
| 52 | +client = TFEClient() |
| 53 | + |
| 54 | +# Create |
| 55 | +stack = client.stacks.create( |
| 56 | + StackCreateOptions( |
| 57 | + name="k8s-stack", |
| 58 | + project=Project(id="prj-abc123"), |
| 59 | + vcs_repo=VCSRepo( |
| 60 | + identifier="my-org/k8s-stack", |
| 61 | + branch="main", |
| 62 | + oauth_token_id="ot-abc123", |
| 63 | + ), |
| 64 | + ) |
| 65 | +) |
| 66 | +print(stack.id, stack.name) |
| 67 | + |
| 68 | +# List |
| 69 | +for stack in client.stacks.list("my-org", StackListOptions(page_size=20)): |
| 70 | + print(stack.id, stack.name, stack.deployment_names) |
| 71 | + |
| 72 | +# Read / update / delete |
| 73 | +stack = client.stacks.read("st-abc123") |
| 74 | +client.stacks.delete("st-abc123") |
| 75 | +``` |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## Stack configurations (`client.stack_configurations`) |
| 80 | + |
| 81 | +A stack configuration is a versioned snapshot of the stack's source, |
| 82 | +created whenever a VCS commit triggers preparation. Its `status` progresses |
| 83 | +from `pending` through `converging` to `converged` (or `errored` if |
| 84 | +preparation fails). Check `client.stack_diagnostics` for details when a |
| 85 | +configuration errors. |
| 86 | + |
| 87 | +| Method | Purpose | |
| 88 | +|---|---| |
| 89 | +| `client.stack_configurations.create(stack_id, options)` | Create (trigger preparation of) a new configuration. | |
| 90 | +| `client.stack_configurations.list(stack_id, options=None)` | Iterate configurations for a stack, newest first. | |
| 91 | +| `client.stack_configurations.read(configuration_id, options=None)` | Read a configuration, optionally with included relationships. | |
| 92 | + |
| 93 | +```python |
| 94 | +from pytfe.models import ( |
| 95 | + StackConfigurationCreateOptions, |
| 96 | + StackConfigurationIncludeOps, |
| 97 | + StackConfigurationReadOptions, |
| 98 | + StackConfigurationSource, |
| 99 | +) |
| 100 | + |
| 101 | +# Trigger preparation from the latest VCS commit |
| 102 | +config = client.stack_configurations.create( |
| 103 | + "st-abc123", |
| 104 | + StackConfigurationCreateOptions(source=StackConfigurationSource.FETCH), |
| 105 | +) |
| 106 | +print(config.id, config.status) |
| 107 | + |
| 108 | +# Read with diagnostics included |
| 109 | +config = client.stack_configurations.read( |
| 110 | + "stc-abc123", |
| 111 | + StackConfigurationReadOptions( |
| 112 | + include=[StackConfigurationIncludeOps.STACK_DIAGNOSTICS] |
| 113 | + ), |
| 114 | +) |
| 115 | + |
| 116 | +# Iterate configurations for a stack |
| 117 | +for config in client.stack_configurations.list("st-abc123"): |
| 118 | + print(config.id, config.status, config.sequence_number) |
| 119 | +``` |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## Stack configuration summaries (`client.stack_configuration_summaries`) |
| 124 | + |
| 125 | +Lightweight rollup of status and deployment counts per configuration — useful |
| 126 | +for dashboards without fetching every configuration individually. Each summary |
| 127 | +also carries `group_status_summary` and `run_status_summary` objects with |
| 128 | +aggregated counts across all deployment groups and runs. |
| 129 | + |
| 130 | +| Method | Purpose | |
| 131 | +|---|---| |
| 132 | +| `client.stack_configuration_summaries.list(stack_id, options=None)` | Iterate configuration summaries for a stack, newest first. | |
| 133 | + |
| 134 | +```python |
| 135 | +from pytfe.models import StackConfigurationSummaryListOptions |
| 136 | + |
| 137 | +for summary in client.stack_configuration_summaries.list("st-abc123"): |
| 138 | + print(summary.id, f"seq={summary.sequence_number}", summary.status) |
| 139 | + if summary.group_status_summary: |
| 140 | + g = summary.group_status_summary |
| 141 | + print(f" groups: succeeded={g.succeeded} failed={g.failed}") |
| 142 | + if summary.run_status_summary: |
| 143 | + r = summary.run_status_summary |
| 144 | + print(f" runs: succeeded={r.succeeded} failed={r.failed}") |
| 145 | +``` |
| 146 | + |
| 147 | +--- |
| 148 | + |
| 149 | +## Stack deployments (`client.stack_deployments`) |
| 150 | + |
| 151 | +A stack deployment represents one named environment (e.g. `dev`, `staging`, |
| 152 | +`prod`) that receives configuration changes. Deployments are defined in the |
| 153 | +stack's source and tracked here for status and history. |
| 154 | + |
| 155 | +| Method | Purpose | |
| 156 | +|---|---| |
| 157 | +| `client.stack_deployments.list(stack_id, options=None)` | Iterate deployments for a stack. | |
| 158 | + |
| 159 | +```python |
| 160 | +for deployment in client.stack_deployments.list("st-abc123"): |
| 161 | + print(deployment.id, deployment.name) |
| 162 | +``` |
| 163 | + |
| 164 | +--- |
| 165 | + |
| 166 | +## Stack deployment groups (`client.stack_deployment_groups`) |
| 167 | + |
| 168 | +A deployment group coordinates the plan and apply runs for one deployment |
| 169 | +within a configuration. Use `approve_all_plans` to advance pending plan steps, |
| 170 | +or `rerun` to retry specific failed deployments. |
| 171 | + |
| 172 | +| Method | Purpose | |
| 173 | +|---|---| |
| 174 | +| `client.stack_deployment_groups.list(configuration_id, options=None)` | Iterate deployment groups for a configuration. | |
| 175 | +| `client.stack_deployment_groups.read(group_id)` | Read a deployment group. | |
| 176 | +| `client.stack_deployment_groups.read_by_name(configuration_id, name)` | Read a deployment group by its deployment name. | |
| 177 | +| `client.stack_deployment_groups.approve_all_plans(group_id)` | Approve all pending plan steps in the group. | |
| 178 | +| `client.stack_deployment_groups.rerun(group_id, options)` | Rerun specific failed deployments in the group. | |
| 179 | + |
| 180 | +```python |
| 181 | +from pytfe.models import StackDeploymentGroupRerunOptions |
| 182 | + |
| 183 | +# List all groups for a configuration |
| 184 | +for group in client.stack_deployment_groups.list("stc-abc123"): |
| 185 | + print(group.id, group.status) |
| 186 | + |
| 187 | +# Read by deployment name |
| 188 | +dev_group = client.stack_deployment_groups.read_by_name("stc-abc123", "dev") |
| 189 | + |
| 190 | +# Approve all pending plans |
| 191 | +client.stack_deployment_groups.approve_all_plans("sdg-abc123") |
| 192 | + |
| 193 | +# Rerun failed deployments |
| 194 | +client.stack_deployment_groups.rerun( |
| 195 | + "sdg-abc123", |
| 196 | + StackDeploymentGroupRerunOptions(deployments=["dev", "staging"]), |
| 197 | +) |
| 198 | +``` |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +## Stack deployment group summaries (`client.stack_deployment_group_summaries`) |
| 203 | + |
| 204 | +Per-group rollup of run counts within a configuration — one record per |
| 205 | +deployment group, with `status_counts` broken down by run status. |
| 206 | + |
| 207 | +| Method | Purpose | |
| 208 | +|---|---| |
| 209 | +| `client.stack_deployment_group_summaries.list(configuration_id, options=None)` | Iterate group summaries for a configuration. | |
| 210 | + |
| 211 | +```python |
| 212 | +for summary in client.stack_deployment_group_summaries.list("stc-abc123"): |
| 213 | + print(summary.name, summary.status) |
| 214 | + if summary.status_counts: |
| 215 | + c = summary.status_counts |
| 216 | + print( |
| 217 | + f" pending={c.pending} deploying={c.deploying} " |
| 218 | + f"succeeded={c.succeeded} failed={c.failed}" |
| 219 | + ) |
| 220 | +``` |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## Stack deployment runs (`client.stack_deployment_runs`) |
| 225 | + |
| 226 | +A deployment run is the individual plan + apply execution within a deployment |
| 227 | +group. Each run progresses through statuses such as `pre-deploying`, |
| 228 | +`deploying`, `pending-operator`, `succeeded`, or `failed`. |
| 229 | + |
| 230 | +| Method | Purpose | |
| 231 | +|---|---| |
| 232 | +| `client.stack_deployment_runs.list(group_id, options=None)` | Iterate runs for a deployment group. | |
| 233 | +| `client.stack_deployment_runs.read(run_id, options=None)` | Read a run, optionally with included relationships. | |
| 234 | +| `client.stack_deployment_runs.approve_all_plans(run_id)` | Approve all pending plan steps in the run. | |
| 235 | +| `client.stack_deployment_runs.cancel(run_id)` | Cancel an in-progress run. | |
| 236 | + |
| 237 | +```python |
| 238 | +from pytfe.models import StackDeploymentRunIncludeOpt, StackDeploymentRunReadOptions |
| 239 | + |
| 240 | +# List runs in a deployment group |
| 241 | +for run in client.stack_deployment_runs.list("sdg-abc123"): |
| 242 | + print(run.id, run.status) |
| 243 | + |
| 244 | +# Read with relationships |
| 245 | +run = client.stack_deployment_runs.read( |
| 246 | + "sdr-abc123", |
| 247 | + StackDeploymentRunReadOptions( |
| 248 | + include=[StackDeploymentRunIncludeOpt.STACK_DEPLOYMENT_GROUP] |
| 249 | + ), |
| 250 | +) |
| 251 | + |
| 252 | +# Cancel |
| 253 | +client.stack_deployment_runs.cancel("sdr-abc123") |
| 254 | +``` |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +## Stack deployment steps (`client.stack_deployment_steps`) |
| 259 | + |
| 260 | +Steps are the granular plan and apply operations within a run. A step in |
| 261 | +`pending-operator` status requires an explicit `advance()` call before the |
| 262 | +deployment can proceed — this is the operator approval gate. |
| 263 | + |
| 264 | +| Method | Purpose | |
| 265 | +|---|---| |
| 266 | +| `client.stack_deployment_steps.list(run_id, options=None)` | Iterate steps for a run. | |
| 267 | +| `client.stack_deployment_steps.read(step_id, options=None)` | Read a step, optionally with included relationships. | |
| 268 | +| `client.stack_deployment_steps.advance(step_id)` | Approve a `pending-operator` step to allow it to proceed. | |
| 269 | +| `client.stack_deployment_steps.list_diagnostics(step_id, options=None)` | Iterate diagnostics attached to a step. | |
| 270 | +| `client.stack_deployment_steps.download_artifact(step_id, artifact_type)` | Download a step artifact as raw bytes. | |
| 271 | + |
| 272 | +Artifact types: `PLAN_DESCRIPTION`, `APPLY_DESCRIPTION`, `PLAN_DEBUG_LOG`, |
| 273 | +`APPLY_DEBUG_LOG`. |
| 274 | + |
| 275 | +```python |
| 276 | +from pytfe.models import StackDeploymentStepArtifactType |
| 277 | + |
| 278 | +for step in client.stack_deployment_steps.list("sdr-abc123"): |
| 279 | + print(step.id, step.operation_type, step.status) |
| 280 | + |
| 281 | +# Advance a step waiting for operator approval |
| 282 | +client.stack_deployment_steps.advance("sds-abc123") |
| 283 | + |
| 284 | +# Download the plan description |
| 285 | +plan_bytes = client.stack_deployment_steps.download_artifact( |
| 286 | + "sds-abc123", |
| 287 | + StackDeploymentStepArtifactType.PLAN_DESCRIPTION, |
| 288 | +) |
| 289 | +print(plan_bytes.decode()) |
| 290 | + |
| 291 | +# List diagnostics for a failed step |
| 292 | +for diag in client.stack_deployment_steps.list_diagnostics("sds-abc123"): |
| 293 | + print(diag.id, diag.severity, diag.summary) |
| 294 | +``` |
| 295 | + |
| 296 | +--- |
| 297 | + |
| 298 | +## Stack states (`client.stack_states`) |
| 299 | + |
| 300 | +A stack state captures the Terraform state snapshot for one deployment at a |
| 301 | +point in time. The `is_current` flag identifies the live state for each |
| 302 | +deployment. Each state carries a `components` list describing which stack |
| 303 | +components contributed to the snapshot. |
| 304 | + |
| 305 | +| Method | Purpose | |
| 306 | +|---|---| |
| 307 | +| `client.stack_states.list(stack_id, options=None)` | Iterate all state snapshots for a stack across all deployments. | |
| 308 | +| `client.stack_states.read(state_id)` | Read a single state snapshot. | |
| 309 | +| `client.stack_states.download_description(state_id)` | Download the raw state description bytes. | |
| 310 | + |
| 311 | +```python |
| 312 | +from pytfe.models import StackStateListOptions |
| 313 | + |
| 314 | +# Current state per deployment |
| 315 | +for state in client.stack_states.list("st-abc123"): |
| 316 | + if state.is_current: |
| 317 | + print( |
| 318 | + state.id, |
| 319 | + f"deployment={state.deployment}", |
| 320 | + f"resources={state.resource_instance_count}", |
| 321 | + ) |
| 322 | + for comp in state.components: |
| 323 | + print(f" component={comp.address}") |
| 324 | + |
| 325 | +# Download raw state description (treat as sensitive) |
| 326 | +raw = client.stack_states.download_description("sts-abc123") |
| 327 | +``` |
| 328 | + |
| 329 | +The description bytes are a JSON blob containing resource instance details. |
| 330 | +Treat them as sensitive — they may contain provider credentials or other |
| 331 | +secret material. |
| 332 | + |
| 333 | +--- |
| 334 | + |
| 335 | +## Stack diagnostics (`client.stack_diagnostics`) |
| 336 | + |
| 337 | +Diagnostics are error or warning records attached to a configuration or a |
| 338 | +deployment step. They surface problems such as provider checksum mismatches, |
| 339 | +deprecated filename extensions, or validation failures. Acknowledging a |
| 340 | +diagnostic marks it as reviewed. |
| 341 | + |
| 342 | +Diagnostic IDs use the `std-` prefix. |
| 343 | + |
| 344 | +| Method | Purpose | |
| 345 | +|---|---| |
| 346 | +| `client.stack_diagnostics.read(diagnostic_id)` | Read a stack diagnostic. | |
| 347 | +| `client.stack_diagnostics.acknowledge(diagnostic_id)` | Acknowledge a diagnostic (mark as reviewed). | |
| 348 | + |
| 349 | +```python |
| 350 | +diag = client.stack_diagnostics.read("std-abc123") |
| 351 | +print(diag.severity, diag.summary) |
| 352 | +print(diag.detail) |
| 353 | + |
| 354 | +# diags is populated when the server rolls up multiple sub-diagnostics |
| 355 | +if diag.diags: |
| 356 | + for nested in diag.diags: |
| 357 | + print(" ", nested.get("severity"), nested.get("summary")) |
| 358 | + |
| 359 | +if not diag.acknowledged: |
| 360 | + client.stack_diagnostics.acknowledge("std-abc123") |
| 361 | +``` |
0 commit comments