|
| 1 | +""" |
| 2 | +Terraform Cloud/Enterprise Workspace Run Task Management Example |
| 3 | +
|
| 4 | +This example demonstrates comprehensive workspace run task operations using the python-tfe SDK. |
| 5 | +It provides a command-line interface for managing workspace run tasks with various operations |
| 6 | +including attach/create, read, update, delete, and listing attached tasks. |
| 7 | +
|
| 8 | +Prerequisites: |
| 9 | + - Set TFE_TOKEN environment variable with your Terraform Cloud API token |
| 10 | + - Ensure you have access to the target organization and workspaces |
| 11 | + - Run tasks must exist in the organization before attaching to workspaces |
| 12 | +
|
| 13 | +Basic Usage: |
| 14 | + python examples/workspace_run_task.py --help |
| 15 | +
|
| 16 | +Core Operations: |
| 17 | +
|
| 18 | +1. List Workspace Run Tasks (default operation): |
| 19 | + python examples/workspace_run_task.py --workspace-id ws-abc123 |
| 20 | + python examples/workspace_run_task.py --workspace-id ws-abc123 --page-size 20 |
| 21 | +
|
| 22 | +2. Attach Run Task to Workspace (Create): |
| 23 | + python examples/workspace_run_task.py --workspace-id ws-abc123 --run-task-id task-def456 --create --enforcement-level mandatory --stages pre-plan post-plan |
| 24 | +
|
| 25 | +3. Read Workspace Run Task Details: |
| 26 | + python examples/workspace_run_task.py --workspace-id ws-abc123 --workspace-task-id wstask-xyz789 |
| 27 | +
|
| 28 | +4. Update Workspace Run Task: |
| 29 | + python examples/workspace_run_task.py --workspace-id ws-abc123 --workspace-task-id wstask-xyz789 --update --enforcement-level advisory --stages pre-plan |
| 30 | +
|
| 31 | +5. Delete Workspace Run Task: |
| 32 | + python examples/workspace_run_task.py --workspace-id ws-abc123 --workspace-task-id wstask-xyz789 --delete |
| 33 | +""" |
| 34 | + |
| 35 | +from __future__ import annotations |
| 36 | + |
| 37 | +import argparse |
| 38 | +import os |
| 39 | + |
| 40 | +from pytfe import TFEClient, TFEConfig |
| 41 | +from pytfe.models import ( |
| 42 | + RunTask, |
| 43 | + Stage, |
| 44 | + TaskEnforcementLevel, |
| 45 | + WorkspaceRunTaskCreateOptions, |
| 46 | + WorkspaceRunTaskListOptions, |
| 47 | + WorkspaceRunTaskUpdateOptions, |
| 48 | +) |
| 49 | + |
| 50 | +# Ensure models are fully rebuilt to resolve forward references |
| 51 | +WorkspaceRunTaskUpdateOptions.model_rebuild() |
| 52 | +WorkspaceRunTaskCreateOptions.model_rebuild() |
| 53 | + |
| 54 | + |
| 55 | +def _print_header(title: str) -> None: |
| 56 | + """Print a formatted header for operations.""" |
| 57 | + print("\n" + "=" * 80) |
| 58 | + print(title) |
| 59 | + print("=" * 80) |
| 60 | + |
| 61 | + |
| 62 | +def main(): |
| 63 | + parser = argparse.ArgumentParser( |
| 64 | + description="Workspace Run Task demo for python-tfe SDK" |
| 65 | + ) |
| 66 | + parser.add_argument( |
| 67 | + "--address", default=os.getenv("TFE_ADDRESS", "https://app.terraform.io") |
| 68 | + ) |
| 69 | + parser.add_argument("--token", default=os.getenv("TFE_TOKEN", "")) |
| 70 | + parser.add_argument("--workspace-id", required=True, help="Workspace ID") |
| 71 | + parser.add_argument( |
| 72 | + "--run-task-id", help="Run Task ID to attach (for create operation)" |
| 73 | + ) |
| 74 | + parser.add_argument( |
| 75 | + "--workspace-task-id", help="Workspace Run Task ID for read/update/delete" |
| 76 | + ) |
| 77 | + parser.add_argument( |
| 78 | + "--create", action="store_true", help="Create/attach a workspace run task" |
| 79 | + ) |
| 80 | + parser.add_argument( |
| 81 | + "--update", action="store_true", help="Update a workspace run task" |
| 82 | + ) |
| 83 | + parser.add_argument( |
| 84 | + "--delete", action="store_true", help="Delete a workspace run task" |
| 85 | + ) |
| 86 | + parser.add_argument( |
| 87 | + "--enforcement-level", |
| 88 | + choices=["advisory", "mandatory"], |
| 89 | + help="Enforcement level for create/update", |
| 90 | + ) |
| 91 | + parser.add_argument( |
| 92 | + "--stages", |
| 93 | + nargs="+", |
| 94 | + choices=["pre-plan", "post-plan", "pre-apply", "post-apply"], |
| 95 | + help="Stages to run the task in (for create/update)", |
| 96 | + ) |
| 97 | + parser.add_argument( |
| 98 | + "--stage", |
| 99 | + choices=["pre-plan", "post-plan", "pre-apply", "post-apply"], |
| 100 | + help="Deprecated: Single stage to run the task in (use --stages instead)", |
| 101 | + ) |
| 102 | + parser.add_argument("--page", type=int, default=1, help="Page number for listing") |
| 103 | + parser.add_argument( |
| 104 | + "--page-size", type=int, default=10, help="Page size for listing" |
| 105 | + ) |
| 106 | + args = parser.parse_args() |
| 107 | + |
| 108 | + cfg = TFEConfig(address=args.address, token=args.token) |
| 109 | + client = TFEClient(cfg) |
| 110 | + |
| 111 | + # Create a new workspace run task (attach run task to workspace) |
| 112 | + if args.create: |
| 113 | + if not args.run_task_id: |
| 114 | + print("Error: --run-task-id is required for creating a workspace run task") |
| 115 | + return |
| 116 | + |
| 117 | + if not args.enforcement_level: |
| 118 | + print("Error: --enforcement-level is required for creating") |
| 119 | + return |
| 120 | + |
| 121 | + _print_header("Creating Workspace Run Task") |
| 122 | + |
| 123 | + # Convert enforcement level string to enum |
| 124 | + enforcement_level = ( |
| 125 | + TaskEnforcementLevel.MANDATORY |
| 126 | + if args.enforcement_level == "mandatory" |
| 127 | + else TaskEnforcementLevel.ADVISORY |
| 128 | + ) |
| 129 | + |
| 130 | + # Convert stages to enum |
| 131 | + stages = None |
| 132 | + if args.stages: |
| 133 | + stages = [] |
| 134 | + for stage_str in args.stages: |
| 135 | + if stage_str == "pre-plan": |
| 136 | + stages.append(Stage.PRE_PLAN) |
| 137 | + elif stage_str == "post-plan": |
| 138 | + stages.append(Stage.POST_PLAN) |
| 139 | + elif stage_str == "pre-apply": |
| 140 | + stages.append(Stage.PRE_APPLY) |
| 141 | + elif stage_str == "post-apply": |
| 142 | + stages.append(Stage.POST_APPLY) |
| 143 | + |
| 144 | + # Deprecated stage support |
| 145 | + stage = None |
| 146 | + if args.stage: |
| 147 | + if args.stage == "pre-plan": |
| 148 | + stage = Stage.PRE_PLAN |
| 149 | + elif args.stage == "post-plan": |
| 150 | + stage = Stage.POST_PLAN |
| 151 | + elif args.stage == "pre-apply": |
| 152 | + stage = Stage.PRE_APPLY |
| 153 | + elif args.stage == "post-apply": |
| 154 | + stage = Stage.POST_APPLY |
| 155 | + |
| 156 | + # Create RunTask object with just ID (minimal required) |
| 157 | + run_task = RunTask( |
| 158 | + id=args.run_task_id, |
| 159 | + name="", # Name not needed for attachment |
| 160 | + url="", # URL not needed for attachment |
| 161 | + category="task", |
| 162 | + enabled=True, |
| 163 | + ) |
| 164 | + |
| 165 | + options = WorkspaceRunTaskCreateOptions( |
| 166 | + enforcement_level=enforcement_level, |
| 167 | + run_task=run_task, |
| 168 | + stages=stages, |
| 169 | + stage=stage, |
| 170 | + ) |
| 171 | + |
| 172 | + try: |
| 173 | + workspace_task = client.workspace_run_tasks.create( |
| 174 | + args.workspace_id, options |
| 175 | + ) |
| 176 | + print("✓ Successfully attached run task to workspace") |
| 177 | + print(f" Workspace Task ID: {workspace_task.id}") |
| 178 | + print(f" Enforcement Level: {workspace_task.enforcement_level.value}") |
| 179 | + print(f" Stage: {workspace_task.stage.value}") |
| 180 | + if workspace_task.stages: |
| 181 | + print(f" Stages: {[s.value for s in workspace_task.stages]}") |
| 182 | + except Exception as e: |
| 183 | + print(f"✗ Failed to create workspace run task: {e}") |
| 184 | + |
| 185 | + # Update an existing workspace run task |
| 186 | + elif args.update: |
| 187 | + if not args.workspace_task_id: |
| 188 | + print("Error: --workspace-task-id is required for updating") |
| 189 | + return |
| 190 | + |
| 191 | + _print_header("Updating Workspace Run Task") |
| 192 | + |
| 193 | + # Build update options |
| 194 | + enforcement_level = None |
| 195 | + if args.enforcement_level: |
| 196 | + enforcement_level = ( |
| 197 | + TaskEnforcementLevel.MANDATORY |
| 198 | + if args.enforcement_level == "mandatory" |
| 199 | + else TaskEnforcementLevel.ADVISORY |
| 200 | + ) |
| 201 | + |
| 202 | + # Update stages if provided |
| 203 | + stages = None |
| 204 | + if args.stages: |
| 205 | + stages = [] |
| 206 | + for stage_str in args.stages: |
| 207 | + if stage_str == "pre-plan": |
| 208 | + stages.append(Stage.PRE_PLAN) |
| 209 | + elif stage_str == "post-plan": |
| 210 | + stages.append(Stage.POST_PLAN) |
| 211 | + elif stage_str == "pre-apply": |
| 212 | + stages.append(Stage.PRE_APPLY) |
| 213 | + elif stage_str == "post-apply": |
| 214 | + stages.append(Stage.POST_APPLY) |
| 215 | + |
| 216 | + options = WorkspaceRunTaskUpdateOptions( |
| 217 | + enforcement_level=enforcement_level, stages=stages |
| 218 | + ) |
| 219 | + |
| 220 | + # Update stage if provided (deprecated) |
| 221 | + if args.stage: |
| 222 | + if args.stage == "pre-plan": |
| 223 | + options.stage = Stage.PRE_PLAN |
| 224 | + elif args.stage == "post-plan": |
| 225 | + options.stage = Stage.POST_PLAN |
| 226 | + elif args.stage == "pre-apply": |
| 227 | + options.stage = Stage.PRE_APPLY |
| 228 | + elif args.stage == "post-apply": |
| 229 | + options.stage = Stage.POST_APPLY |
| 230 | + |
| 231 | + try: |
| 232 | + workspace_task = client.workspace_run_tasks.update( |
| 233 | + args.workspace_id, args.workspace_task_id, options |
| 234 | + ) |
| 235 | + print("✓ Successfully updated workspace run task") |
| 236 | + print(f" Workspace Task ID: {workspace_task.id}") |
| 237 | + print(f" Enforcement Level: {workspace_task.enforcement_level.value}") |
| 238 | + print(f" Stage: {workspace_task.stage.value}") |
| 239 | + if workspace_task.stages: |
| 240 | + print(f" Stages: {[s.value for s in workspace_task.stages]}") |
| 241 | + except Exception as e: |
| 242 | + print(f"✗ Failed to update workspace run task: {e}") |
| 243 | + |
| 244 | + # Delete a workspace run task |
| 245 | + elif args.delete: |
| 246 | + if not args.workspace_task_id: |
| 247 | + print("Error: --workspace-task-id is required for deleting") |
| 248 | + return |
| 249 | + |
| 250 | + _print_header("Deleting Workspace Run Task") |
| 251 | + |
| 252 | + try: |
| 253 | + client.workspace_run_tasks.delete(args.workspace_id, args.workspace_task_id) |
| 254 | + print( |
| 255 | + f"✓ Successfully deleted workspace run task: {args.workspace_task_id}" |
| 256 | + ) |
| 257 | + except Exception as e: |
| 258 | + print(f"✗ Failed to delete workspace run task: {e}") |
| 259 | + |
| 260 | + # Read a specific workspace run task |
| 261 | + elif args.workspace_task_id: |
| 262 | + _print_header("Reading Workspace Run Task") |
| 263 | + |
| 264 | + try: |
| 265 | + workspace_task = client.workspace_run_tasks.read( |
| 266 | + args.workspace_id, args.workspace_task_id |
| 267 | + ) |
| 268 | + print("✓ Workspace Run Task Details:") |
| 269 | + print(f" ID: {workspace_task.id}") |
| 270 | + print(f" Enforcement Level: {workspace_task.enforcement_level.value}") |
| 271 | + print(f" Stage (deprecated): {workspace_task.stage.value}") |
| 272 | + if workspace_task.stages: |
| 273 | + print(f" Stages: {[s.value for s in workspace_task.stages]}") |
| 274 | + if workspace_task.run_task: |
| 275 | + print(f" Run Task ID: {workspace_task.run_task.id}") |
| 276 | + if workspace_task.workspace: |
| 277 | + print(f" Workspace ID: {workspace_task.workspace.id}") |
| 278 | + except Exception as e: |
| 279 | + print(f"✗ Failed to read workspace run task: {e}") |
| 280 | + |
| 281 | + # List all workspace run tasks (default operation) |
| 282 | + else: |
| 283 | + _print_header(f"Listing Workspace Run Tasks for Workspace: {args.workspace_id}") |
| 284 | + |
| 285 | + options = WorkspaceRunTaskListOptions( |
| 286 | + page_number=args.page, |
| 287 | + page_size=args.page_size, |
| 288 | + ) |
| 289 | + |
| 290 | + try: |
| 291 | + count = 0 |
| 292 | + for workspace_task in client.workspace_run_tasks.list( |
| 293 | + args.workspace_id, options |
| 294 | + ): |
| 295 | + count += 1 |
| 296 | + print(f"\n{count}. Workspace Run Task ID: {workspace_task.id}") |
| 297 | + print(f" Enforcement Level: {workspace_task.enforcement_level.value}") |
| 298 | + print(f" Stage: {workspace_task.stage.value}") |
| 299 | + if workspace_task.stages: |
| 300 | + print(f" Stages: {[s.value for s in workspace_task.stages]}") |
| 301 | + if workspace_task.run_task: |
| 302 | + print(f" Run Task ID: {workspace_task.run_task.id}") |
| 303 | + |
| 304 | + if count == 0: |
| 305 | + print("No workspace run tasks found for this workspace.") |
| 306 | + else: |
| 307 | + print(f"\n✓ Total workspace run tasks listed: {count}") |
| 308 | + except Exception as e: |
| 309 | + print(f"✗ Failed to list workspace run tasks: {e}") |
| 310 | + |
| 311 | + |
| 312 | +if __name__ == "__main__": |
| 313 | + main() |
0 commit comments