Skip to content

Commit 41f5977

Browse files
Add task_result and task_stages models matching go-tfe
- Add task_result.py with TaskResult, TaskResultStatus, TaskEnforcementLevel models - Add task_stages.py with TaskStage, Stage, TaskStageStatus, Actions, Permissions models - Update run_task.py to import Stage and TaskEnforcementLevel from new modules (remove duplicates) - Update run_tasks_integration.py to use TaskResultStatus enum from task_result - Update run_task_request.py to add model_config for proper serialization - Export all new models in __init__.py - All 22 unit tests passing - Matches go-tfe implementation structure
1 parent 2bae7d2 commit 41f5977

6 files changed

Lines changed: 247 additions & 27 deletions

File tree

src/pytfe/models/__init__.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,28 @@
253253
RunTaskListOptions,
254254
RunTaskReadOptions,
255255
RunTaskUpdateOptions,
256-
Stage,
257-
TaskEnforcementLevel,
258256
)
259257
from .run_task_request import (
260258
RunTaskRequest,
261259
RunTaskRequestCapabilities,
262260
)
261+
from .task_result import (
262+
TaskEnforcementLevel,
263+
TaskResult,
264+
TaskResultStatus,
265+
TaskResultStatusTimestamps,
266+
)
267+
from .task_stages import (
268+
Actions,
269+
Permissions,
270+
Stage,
271+
TaskStage,
272+
TaskStageListOptions,
273+
TaskStageOverrideOptions,
274+
TaskStageReadOptions,
275+
TaskStageStatus,
276+
TaskStageStatusTimestamps,
277+
)
263278
from .run_trigger import (
264279
RunTrigger,
265280
RunTriggerCreateOptions,

src/pytfe/models/run_task.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from ..models.common import Pagination
88
from .agent import AgentPool
99
from .organization import Organization
10+
from .task_result import TaskEnforcementLevel
11+
from .task_stages import Stage
1012
from .workspace_run_task import WorkspaceRunTask
1113

1214

@@ -37,18 +39,6 @@ class GlobalRunTaskOptions(BaseModel):
3739
enforcement_level: TaskEnforcementLevel | None = None
3840

3941

40-
class Stage(str, Enum):
41-
PRE_PLAN = "pre-plan"
42-
POST_PLAN = "post-plan"
43-
PRE_APPLY = "pre-apply"
44-
POST_APPLY = "post-apply"
45-
46-
47-
class TaskEnforcementLevel(str, Enum):
48-
ADVISORY = "advisory"
49-
MANDATORY = "mandatory"
50-
51-
5242
class RunTaskIncludeOptions(str, Enum):
5343
RUN_TASK_WORKSPACE_TASKS = "workspace_tasks"
5444
RUN_TASK_WORKSPACE = "workspace_tasks.workspace"

src/pytfe/models/run_task_request.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class RunTaskRequestCapabilities(BaseModel):
1818
default=False,
1919
description="Whether the run task server supports outcomes"
2020
)
21+
22+
model_config = ConfigDict(populate_by_name=True)
2123

2224

2325
class RunTaskRequest(BaseModel):

src/pytfe/models/task_result.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Task Result models for python-tfe.
2+
3+
This module contains models related to task results in Terraform Cloud/Enterprise.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from datetime import datetime
9+
from enum import Enum
10+
from typing import TYPE_CHECKING
11+
12+
from pydantic import BaseModel, ConfigDict, Field
13+
14+
if TYPE_CHECKING:
15+
from .task_stages import TaskStage
16+
17+
18+
class TaskResultStatus(str, Enum):
19+
"""Task result status enum."""
20+
21+
PASSED = "passed"
22+
FAILED = "failed"
23+
PENDING = "pending"
24+
RUNNING = "running"
25+
UNREACHABLE = "unreachable"
26+
ERRORED = "errored"
27+
28+
29+
class TaskEnforcementLevel(str, Enum):
30+
"""Task enforcement level enum."""
31+
32+
ADVISORY = "advisory"
33+
MANDATORY = "mandatory"
34+
35+
36+
class TaskResultStatusTimestamps(BaseModel):
37+
"""Timestamps recorded for a task result."""
38+
39+
errored_at: datetime | None = Field(default=None, alias="errored-at")
40+
running_at: datetime | None = Field(default=None, alias="running-at")
41+
canceled_at: datetime | None = Field(default=None, alias="canceled-at")
42+
failed_at: datetime | None = Field(default=None, alias="failed-at")
43+
passed_at: datetime | None = Field(default=None, alias="passed-at")
44+
45+
model_config = ConfigDict(populate_by_name=True)
46+
47+
48+
class TaskResult(BaseModel):
49+
"""Represents a HCP Terraform or Terraform Enterprise run task result.
50+
51+
API Documentation:
52+
https://developer.hashicorp.com/terraform/cloud-docs/api-docs/task-results
53+
"""
54+
55+
id: str
56+
status: TaskResultStatus
57+
message: str
58+
status_timestamps: TaskResultStatusTimestamps = Field(alias="status-timestamps")
59+
url: str
60+
created_at: datetime = Field(alias="created-at")
61+
updated_at: datetime = Field(alias="updated-at")
62+
task_id: str = Field(alias="task-id")
63+
task_name: str = Field(alias="task-name")
64+
task_url: str = Field(alias="task-url")
65+
workspace_task_id: str = Field(alias="workspace-task-id")
66+
workspace_task_enforcement_level: TaskEnforcementLevel = Field(
67+
alias="workspace-task-enforcement-level"
68+
)
69+
agent_pool_id: str | None = Field(default=None, alias="agent-pool-id")
70+
71+
# Relationships
72+
task_stage: TaskStage | None = Field(default=None, alias="task-stage")
73+
74+
model_config = ConfigDict(populate_by_name=True)

src/pytfe/models/task_stages.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Task Stage models for python-tfe.
2+
3+
This module contains models related to task stages in Terraform Cloud/Enterprise.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from datetime import datetime
9+
from enum import Enum
10+
from typing import TYPE_CHECKING
11+
12+
from pydantic import BaseModel, ConfigDict, Field
13+
14+
if TYPE_CHECKING:
15+
from .policy_evaluation import PolicyEvaluation
16+
from .run import Run
17+
from .task_result import TaskResult
18+
19+
20+
class Stage(str, Enum):
21+
"""Enum representing possible run stages for run tasks."""
22+
23+
PRE_PLAN = "pre-plan"
24+
POST_PLAN = "post-plan"
25+
PRE_APPLY = "pre-apply"
26+
POST_APPLY = "post-apply"
27+
28+
29+
class TaskStageStatus(str, Enum):
30+
"""Enum representing all possible statuses for a task stage."""
31+
32+
PENDING = "pending"
33+
RUNNING = "running"
34+
PASSED = "passed"
35+
FAILED = "failed"
36+
AWAITING_OVERRIDE = "awaiting_override"
37+
CANCELED = "canceled"
38+
ERRORED = "errored"
39+
UNREACHABLE = "unreachable"
40+
41+
42+
class Permissions(BaseModel):
43+
"""Permission types for overriding a task stage."""
44+
45+
can_override_policy: bool | None = Field(default=None, alias="can-override-policy")
46+
can_override_tasks: bool | None = Field(default=None, alias="can-override-tasks")
47+
can_override: bool | None = Field(default=None, alias="can-override")
48+
49+
model_config = ConfigDict(populate_by_name=True)
50+
51+
52+
class Actions(BaseModel):
53+
"""Task stage actions."""
54+
55+
is_overridable: bool | None = Field(default=None, alias="is-overridable")
56+
57+
model_config = ConfigDict(populate_by_name=True)
58+
59+
60+
class TaskStageStatusTimestamps(BaseModel):
61+
"""Timestamps recorded for a task stage."""
62+
63+
errored_at: datetime | None = Field(default=None, alias="errored-at")
64+
running_at: datetime | None = Field(default=None, alias="running-at")
65+
canceled_at: datetime | None = Field(default=None, alias="canceled-at")
66+
failed_at: datetime | None = Field(default=None, alias="failed-at")
67+
passed_at: datetime | None = Field(default=None, alias="passed-at")
68+
69+
model_config = ConfigDict(populate_by_name=True)
70+
71+
72+
class TaskStage(BaseModel):
73+
"""Represents a HCP Terraform or Terraform Enterprise run's task stage.
74+
75+
Task stages are where run tasks can occur during a run lifecycle.
76+
77+
API Documentation:
78+
https://developer.hashicorp.com/terraform/cloud-docs/api-docs/task-stages
79+
"""
80+
81+
id: str
82+
stage: Stage
83+
status: TaskStageStatus
84+
status_timestamps: TaskStageStatusTimestamps = Field(alias="status-timestamps")
85+
created_at: datetime = Field(alias="created-at")
86+
updated_at: datetime = Field(alias="updated-at")
87+
permissions: Permissions | None = None
88+
actions: Actions | None = None
89+
90+
# Relationships
91+
run: Run | None = None
92+
task_results: list[TaskResult] = Field(default_factory=list, alias="task-results")
93+
policy_evaluations: list[PolicyEvaluation] = Field(
94+
default_factory=list,
95+
alias="policy-evaluations"
96+
)
97+
98+
model_config = ConfigDict(populate_by_name=True)
99+
100+
101+
class TaskStageOverrideOptions(BaseModel):
102+
"""Options for overriding a task stage."""
103+
104+
comment: str | None = None
105+
106+
107+
class TaskStageReadOptions(BaseModel):
108+
"""Options for reading a task stage."""
109+
110+
include: list[str] | None = None
111+
112+
113+
class TaskStageListOptions(BaseModel):
114+
"""Options for listing task stages."""
115+
116+
page_number: int | None = Field(default=None, alias="page[number]")
117+
page_size: int | None = Field(default=None, alias="page[size]")
118+
119+
model_config = ConfigDict(populate_by_name=True)

src/pytfe/resources/run_tasks_integration.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,29 @@
99
from typing import Any
1010

1111
from ..errors import TFEError
12+
from ..models.task_result import TaskResultStatus
1213
from ._base import _Service
1314

1415

15-
class TaskResultStatus:
16-
"""Task result status enum."""
17-
18-
PASSED = "passed"
19-
FAILED = "failed"
20-
RUNNING = "running"
21-
22-
2316
class TaskResultTag:
24-
"""Tag to enrich outcomes display in TFC/TFE."""
17+
"""Tag to enrich outcomes display in TFC/TFE.
18+
19+
API Documentation:
20+
https://developer.hashicorp.com/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#severity-and-status-tags
21+
"""
2522

2623
def __init__(self, label: str, level: str | None = None):
24+
"""Initialize a task result tag.
25+
26+
Args:
27+
label: The label for the tag
28+
level: Optional severity level (error, warning, info)
29+
"""
2730
self.label = label
2831
self.level = level
2932

3033
def to_dict(self) -> dict[str, Any]:
31-
"""Convert to dictionary."""
34+
"""Convert to dictionary for JSON serialization."""
3235
result = {"label": self.label}
3336
if self.level:
3437
result["level"] = self.level
@@ -50,14 +53,23 @@ def __init__(
5053
url: str | None = None,
5154
tags: dict[str, list[TaskResultTag]] | None = None,
5255
):
56+
"""Initialize a task result outcome.
57+
58+
Args:
59+
outcome_id: Unique identifier for the outcome
60+
description: Brief description of the outcome
61+
body: Detailed body content (supports markdown)
62+
url: URL to view more details
63+
tags: Dictionary of tag categories to lists of tags
64+
"""
5365
self.outcome_id = outcome_id
5466
self.description = description
5567
self.body = body
5668
self.url = url
5769
self.tags = tags or {}
5870

5971
def to_dict(self) -> dict[str, Any]:
60-
"""Convert to dictionary for JSON serialization."""
72+
"""Convert to dictionary for JSON:API serialization."""
6173
result: dict[str, Any] = {"type": "task-result-outcomes", "attributes": {}}
6274

6375
if self.outcome_id:
@@ -105,8 +117,16 @@ def __init__(
105117
self.outcomes = outcomes or []
106118

107119
def validate(self) -> None:
108-
"""Validate the callback options."""
109-
valid_statuses = [TaskResultStatus.PASSED, TaskResultStatus.FAILED, TaskResultStatus.RUNNING]
120+
"""Validate the callback options.
121+
122+
Only passed, failed, and running statuses are allowed for callbacks.
123+
pending and errored are not valid callback statuses per TFC/TFE API.
124+
"""
125+
valid_statuses = [
126+
TaskResultStatus.PASSED.value,
127+
TaskResultStatus.FAILED.value,
128+
TaskResultStatus.RUNNING.value
129+
]
110130
if self.status not in valid_statuses:
111131
raise TFEError(
112132
f"Invalid task result status: {self.status}. "

0 commit comments

Comments
 (0)