Skip to content

Commit d5e76bd

Browse files
committed
feat: add workspace run task resource and models
1 parent 17fe314 commit d5e76bd

11 files changed

Lines changed: 729 additions & 37 deletions

File tree

examples/workspace_run_task.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Workspace Run Task Operations Example
4+
5+
Demonstrates workspace run task operations:
6+
1. create() - Attach a run task to a workspace
7+
2. list() - List all workspace run tasks for a workspace
8+
3. read() - Read a workspace run task by ID
9+
4. update() - Update enforcement/stage settings
10+
5. delete() - Delete a workspace run task
11+
12+
Prerequisites:
13+
- Set TFE_TOKEN and TFE_ADDRESS environment variables
14+
- Use valid workspace and run task IDs
15+
"""
16+
17+
import argparse
18+
19+
from pytfe import TFEClient, TFEConfig
20+
from pytfe.models import (
21+
RunTask,
22+
WorkspaceRunTaskCreateOptions,
23+
WorkspaceRunTaskUpdateOptions,
24+
)
25+
26+
27+
def _find_matching_workspace_run_task(items, run_task_id: str):
28+
for item in items:
29+
if item.run_task and item.run_task.id == run_task_id:
30+
return item
31+
32+
if len(items) == 1:
33+
return items[0]
34+
35+
return None
36+
37+
38+
def main():
39+
parser = argparse.ArgumentParser(
40+
description="Workspace run task operations demo for python-tfe SDK"
41+
)
42+
parser.add_argument(
43+
"--workspace-id",
44+
required=True,
45+
help="Workspace ID (example: ws-abc123)",
46+
)
47+
parser.add_argument(
48+
"--run-task-id",
49+
required=True,
50+
help="Run task ID (example: task-abc123)",
51+
)
52+
parser.add_argument(
53+
"--delete-existing",
54+
action="store_true",
55+
help="Allow delete() to remove a workspace run task that existed before this example ran",
56+
)
57+
args = parser.parse_args()
58+
59+
client = TFEClient(TFEConfig.from_env())
60+
61+
workspace_id = args.workspace_id
62+
run_task_id = args.run_task_id
63+
64+
if workspace_id == "ws-xxxxxxxx" or run_task_id == "task-xxxxxxxx":
65+
print("Please provide real IDs for --workspace-id and --run-task-id")
66+
return
67+
68+
print("=" * 80)
69+
print("WORKSPACE RUN TASK OPERATIONS")
70+
print("=" * 80)
71+
72+
workspace_task = None
73+
created_in_run = False
74+
75+
print("\n1. create()")
76+
try:
77+
create_options = WorkspaceRunTaskCreateOptions(
78+
enforcement_level="advisory",
79+
run_task=RunTask(id=run_task_id),
80+
stages=["post_plan"],
81+
)
82+
workspace_task = client.workspace_run_tasks.create(workspace_id, create_options)
83+
created_in_run = True
84+
print(f"Created workspace run task: {workspace_task.id}")
85+
except Exception as exc:
86+
print(f"Create result: {exc}")
87+
88+
print("\n2. list()")
89+
items = []
90+
try:
91+
items = list(client.workspace_run_tasks.list(workspace_id))
92+
print(f"Found {len(items)} workspace run task(s)")
93+
for item in items:
94+
run_task = item.run_task.id if item.run_task else None
95+
print(
96+
f"- {item.id} run_task={run_task} enforcement={item.enforcement_level} stages={item.stages}"
97+
)
98+
99+
if workspace_task is None:
100+
workspace_task = _find_matching_workspace_run_task(items, run_task_id)
101+
if workspace_task is not None:
102+
print(f"Using existing workspace run task: {workspace_task.id}")
103+
except Exception as exc:
104+
print(f"List failed: {exc}")
105+
106+
print("\n3. read()")
107+
if workspace_task is None:
108+
print("Read skipped: no workspace run task ID available")
109+
else:
110+
try:
111+
workspace_task = client.workspace_run_tasks.read(
112+
workspace_id, workspace_task.id
113+
)
114+
print(
115+
f"Read workspace run task: {workspace_task.id} enforcement={workspace_task.enforcement_level} stages={workspace_task.stages}"
116+
)
117+
except Exception as exc:
118+
print(f"Read failed: {exc}")
119+
120+
print("\n4. update()")
121+
if workspace_task is None:
122+
print("Update skipped: no workspace run task ID available")
123+
else:
124+
try:
125+
update_options = WorkspaceRunTaskUpdateOptions(
126+
# enforcement_level="mandatory",
127+
stages=["post_plan", "pre_plan"],
128+
)
129+
workspace_task = client.workspace_run_tasks.update(
130+
workspace_id, workspace_task.id, update_options
131+
)
132+
print(
133+
f"Updated workspace run task: {workspace_task.id} enforcement={workspace_task.enforcement_level} stages={workspace_task.stages}"
134+
)
135+
except Exception as exc:
136+
print(f"Update failed: {exc}")
137+
138+
print("\n5. delete()")
139+
if workspace_task is None:
140+
print("Delete skipped: no workspace run task ID available")
141+
elif not created_in_run and not args.delete_existing:
142+
print(
143+
"Delete skipped: workspace run task existed before this example. "
144+
"Re-run with --delete-existing to delete it."
145+
)
146+
else:
147+
try:
148+
client.workspace_run_tasks.delete(workspace_id, workspace_task.id)
149+
print(f"Deleted workspace run task: {workspace_task.id}")
150+
except Exception as exc:
151+
print(f"Delete failed: {exc}")
152+
153+
print("=" * 80)
154+
print("WORKSPACE RUN TASK OPERATIONS COMPLETED")
155+
print("=" * 80)
156+
157+
158+
if __name__ == "__main__":
159+
main()

src/pytfe/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from .resources.variable import Variables
5353
from .resources.variable_sets import VariableSets, VariableSetVariables
5454
from .resources.workspace_resources import WorkspaceResourcesService
55+
from .resources.workspace_run_task import WorkspaceRunTasks
5556
from .resources.workspaces import Workspaces
5657

5758

@@ -103,6 +104,7 @@ def __init__(self, config: TFEConfig | None = None):
103104
self.variable_set_variables = VariableSetVariables(self._transport)
104105
self.workspaces = Workspaces(self._transport)
105106
self.workspace_resources = WorkspaceResourcesService(self._transport)
107+
self.workspace_run_tasks = WorkspaceRunTasks(self._transport)
106108
self.registry_modules = RegistryModules(self._transport)
107109
self.registry_providers = RegistryProviders(self._transport)
108110
self.registry_provider_versions = RegistryProviderVersions(self._transport)

src/pytfe/errors.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,13 @@ def __init__(self, message: str = 'category must be "task"'):
338338
super().__init__(message)
339339

340340

341+
class InvalidWorkspaceRunTaskIDError(InvalidValues):
342+
"""Raised when an invalid workspace run task ID is provided."""
343+
344+
def __init__(self, message: str = "invalid value for workspace run task ID"):
345+
super().__init__(message)
346+
347+
341348
# Run Trigger errors
342349
class RequiredRunTriggerListOpsError(RequiredFieldMissing):
343350
"""Raised when required run trigger list options are missing."""

src/pytfe/models/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@
328328
from .run_task_integration import (
329329
TaskResultStatus as TaskResultCallbackStatus,
330330
)
331+
from .run_task_request import (
332+
RunTaskRequest,
333+
RunTaskRequestCapabilitites,
334+
)
331335
from .run_trigger import (
332336
RunTrigger,
333337
RunTriggerCreateOptions,
@@ -456,6 +460,12 @@
456460
WorkspaceResource,
457461
WorkspaceResourceListOptions,
458462
)
463+
from .workspace_run_task import (
464+
WorkspaceRunTask,
465+
WorkspaceRunTaskCreateOptions,
466+
WorkspaceRunTaskListOptions,
467+
WorkspaceRunTaskUpdateOptions,
468+
)
459469

460470
# ── Public surface ────────────────────────────────────────────────────────────
461471
__all__ = [
@@ -684,6 +694,11 @@
684694
# Workspace Resources
685695
"WorkspaceResource",
686696
"WorkspaceResourceListOptions",
697+
# Workspace Run Tasks
698+
"WorkspaceRunTask",
699+
"WorkspaceRunTaskListOptions",
700+
"WorkspaceRunTaskCreateOptions",
701+
"WorkspaceRunTaskUpdateOptions",
687702
"RunQueue",
688703
"ReadRunQueueOptions",
689704
# Runs
@@ -728,6 +743,9 @@
728743
"RunTaskCreateOptions",
729744
"RunTaskUpdateOptions",
730745
"RunTaskReadOptions",
746+
# Run Task Request
747+
"RunTaskRequest",
748+
"RunTaskRequestCapabilitites",
731749
# Task Result
732750
"TaskResult",
733751
"TaskResultEnforcementLevel",

src/pytfe/models/run_task.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@
44
from __future__ import annotations
55

66
from enum import Enum
7+
from typing import TYPE_CHECKING
78

89
from pydantic import BaseModel, Field
910

1011
from ..models.common import Pagination
1112
from .agent import AgentPool
1213
from .organization import Organization
13-
from .workspace_run_task import WorkspaceRunTask
14+
15+
if TYPE_CHECKING:
16+
from .workspace_run_task import WorkspaceRunTask
1417

1518

1619
class RunTask(BaseModel):
1720
id: str
18-
name: str
21+
name: str | None = None
1922
description: str | None = None
20-
url: str
21-
category: str
23+
url: str | None = None
24+
category: str | None = None
2225
hmac_key: str | None = None
23-
enabled: bool
26+
enabled: bool | None = None
2427
global_configuration: GlobalRunTask | None = None
2528

2629
agent_pool: AgentPool | None = None
@@ -41,10 +44,10 @@ class GlobalRunTaskOptions(BaseModel):
4144

4245

4346
class Stage(str, Enum):
44-
PRE_PLAN = "pre-plan"
45-
POST_PLAN = "post-plan"
46-
PRE_APPLY = "pre-apply"
47-
POST_APPLY = "post-apply"
47+
PRE_PLAN = "pre_plan"
48+
POST_PLAN = "post_plan"
49+
PRE_APPLY = "pre_apply"
50+
POST_APPLY = "post_apply"
4851

4952

5053
class TaskEnforcementLevel(str, Enum):

0 commit comments

Comments
 (0)