Skip to content

Commit f023378

Browse files
authored
Merge branch 'next-1.0.0' into run-task-request
2 parents f064814 + 17fe314 commit f023378

38 files changed

Lines changed: 5484 additions & 7 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# Unreleased
22

3+
34
# Released
5+
# v1.0.0
6+
7+
## Features
8+
9+
### Explorer API
10+
* Added Explorer resource support with query, CSV export, saved view CRUD, saved view result query, and saved view CSV export endpoints.
11+
12+
413
# v0.1.5
514

615
* `pytfe.__version__` added in src/pytfe/init.py via importlib.metadata.version("pytfe"). This will resolve to the version from pyproject.toml.

examples/comment.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright IBM Corp. 2025, 2026
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import os
8+
9+
from pytfe import TFEClient, TFEConfig
10+
from pytfe.models import CommentCreateOptions
11+
12+
13+
def _print_header(title: str):
14+
print("\n" + "=" * 80)
15+
print(title)
16+
print("=" * 80)
17+
18+
19+
def main():
20+
parser = argparse.ArgumentParser(description="Comments demo for python-tfe SDK")
21+
parser.add_argument(
22+
"--address", default=os.getenv("TFE_ADDRESS", "https://app.terraform.io")
23+
)
24+
parser.add_argument("--token", default=os.getenv("TFE_TOKEN", ""))
25+
parser.add_argument("--run-id", required=True, help="Run ID (e.g. run-xxxxx)")
26+
parser.add_argument("--create", action="store_true", help="Create a new comment")
27+
parser.add_argument("--body", help="Comment body text (required with --create)")
28+
parser.add_argument("--read", action="store_true", help="Read a specific comment")
29+
parser.add_argument("--id", help="Comment ID (e.g. com-xxxxx), required for --read")
30+
args = parser.parse_args()
31+
32+
cfg = TFEConfig(address=args.address, token=args.token)
33+
client = TFEClient(cfg)
34+
35+
# 1) Always list existing comments for the run
36+
_print_header(f"Listing comments for run: {args.run_id}")
37+
comment_count = 0
38+
for comment in client.comments.list(run_id=args.run_id):
39+
comment_count += 1
40+
print(f"- ID: {comment.id}")
41+
print(f" Body: {comment.body}")
42+
print()
43+
44+
if comment_count == 0:
45+
print("No comments found.")
46+
else:
47+
print(f"Total: {comment_count} comments")
48+
49+
# 2) Create a new comment
50+
if args.create:
51+
if not args.body:
52+
print("--body is required for --create")
53+
else:
54+
_print_header(f"Creating a comment on run: {args.run_id}")
55+
opts = CommentCreateOptions(body=args.body)
56+
comment = client.comments.create(run_id=args.run_id, options=opts)
57+
print(f"Created comment: {comment.id}")
58+
print(f" Body: {comment.body}")
59+
60+
# 3) Read a specific comment
61+
if args.read:
62+
if not args.id:
63+
print("--id is required for --read")
64+
else:
65+
_print_header(f"Reading comment: {args.id}")
66+
comment = client.comments.read(comment_id=args.id)
67+
print(f"ID: {comment.id}")
68+
print(f"Body: {comment.body}")
69+
70+
71+
if __name__ == "__main__":
72+
main()

examples/explorer.py

Lines changed: 449 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env python3
2+
"""Organization audit configuration operations example.
3+
4+
Demonstrates:
5+
1. read() - read organization audit configuration
6+
2. test() - send a test audit event
7+
3. update() - update organization audit configuration
8+
"""
9+
10+
import os
11+
12+
from pytfe import TFEClient, TFEConfig
13+
from pytfe.errors import TFEError
14+
from pytfe.models import (
15+
OrganizationAuditConfigAuditTrails,
16+
OrganizationAuditConfigurationOptions,
17+
)
18+
19+
20+
def main() -> None:
21+
client = TFEClient(TFEConfig.from_env())
22+
23+
organization_name = os.getenv("TFE_ORG", "example-org")
24+
25+
try:
26+
print("[READ] Reading organization audit configuration")
27+
read_result = client.organization_audit_configurations.read(organization_name)
28+
print(f"[READ] id={read_result.id}, updated_at={read_result.updated_at}")
29+
if read_result.audit_trails is not None:
30+
print(f"[READ] audit_trails_enabled={read_result.audit_trails.enabled}")
31+
32+
print("[TEST] Sending test audit event")
33+
test_result = client.organization_audit_configurations.test(organization_name)
34+
print(f"[TEST] request_id={test_result.request_id}")
35+
36+
print("[UPDATE] Updating organization audit configuration")
37+
options = OrganizationAuditConfigurationOptions(
38+
audit_trails=OrganizationAuditConfigAuditTrails(enabled=True)
39+
)
40+
update_result = client.organization_audit_configurations.update(
41+
organization_name,
42+
options,
43+
)
44+
print(f"[UPDATE] id={update_result.id}, updated_at={update_result.updated_at}")
45+
46+
except TFEError as exc:
47+
print(f"API error: {exc}")
48+
print("Check TFE_TOKEN, TFE_ADDRESS, and TFE_ORG.")
49+
finally:
50+
client.close()
51+
52+
53+
if __name__ == "__main__":
54+
main()

examples/organization_tags.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python3
2+
# Copyright IBM Corp. 2025, 2026
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
"""Organization tags operations example.
6+
7+
Demonstrates:
8+
1. list() - list tags in an organization
9+
2. add_workspaces() - associate a workspace with a tag
10+
3. delete() - delete a tag from an organization
11+
12+
Usage:
13+
python examples/organization_tags.py --org my-org
14+
python examples/organization_tags.py --org my-org --tag-id tag-abc123 --workspace-id ws-xyz
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import argparse
20+
import os
21+
22+
from pytfe import TFEClient, TFEConfig
23+
from pytfe.errors import TFEError
24+
from pytfe.models.organization_tags import (
25+
AddWorkspacesToTagOptions,
26+
OrganizationTagsDeleteOptions,
27+
)
28+
29+
30+
def main() -> None:
31+
parser = argparse.ArgumentParser(
32+
description="Organization Tags demo for python-tfe SDK"
33+
)
34+
parser.add_argument(
35+
"--address", default=os.getenv("TFE_ADDRESS", "https://app.terraform.io")
36+
)
37+
parser.add_argument("--token", default=os.getenv("TFE_TOKEN", ""))
38+
parser.add_argument(
39+
"--org",
40+
default=os.getenv("TFE_ORG", ""),
41+
help="Organization name",
42+
)
43+
parser.add_argument(
44+
"--tag-id",
45+
default=os.getenv("TFE_TAG_ID", ""),
46+
help="Tag ID for add/delete operations",
47+
)
48+
parser.add_argument(
49+
"--workspace-id",
50+
default=os.getenv("TFE_WORKSPACE_ID", ""),
51+
help="Workspace ID to associate with tag",
52+
)
53+
args = parser.parse_args()
54+
55+
if not args.token:
56+
print("Error: TFE_TOKEN environment variable or --token required")
57+
return
58+
59+
if not args.org:
60+
print("Error: TFE_ORG environment variable or --org required")
61+
return
62+
63+
cfg = TFEConfig(address=args.address, token=args.token)
64+
client = TFEClient(cfg)
65+
66+
# 1) List tags
67+
try:
68+
print("[LIST] Listing organization tags")
69+
print(f"[LIST] organization={args.org}")
70+
tags = list(client.organization_tags.list(args.org))
71+
print(f"[LIST] total_tags={len(tags)}")
72+
for tag in tags:
73+
print(
74+
f"[LIST] id={tag.id}, name={tag.name}, instance_count={tag.instance_count}"
75+
)
76+
if not tags:
77+
print("[LIST] no tags found")
78+
except TFEError as exc:
79+
print(f"[LIST] API error: {exc}")
80+
return
81+
82+
if not args.tag_id:
83+
print("[ADD_WORKSPACES] skipped: set --tag-id or TFE_TAG_ID")
84+
print("[DELETE] skipped: set --tag-id or TFE_TAG_ID")
85+
return
86+
87+
# 2) Add workspace to tag
88+
if args.workspace_id:
89+
print("[ADD_WORKSPACES] Associating a workspace to a tag")
90+
print(
91+
f"[ADD_WORKSPACES] organization={args.org}, tag_id={args.tag_id}, workspace_id={args.workspace_id}"
92+
)
93+
try:
94+
client.organization_tags.add_workspaces(
95+
args.org,
96+
args.tag_id,
97+
AddWorkspacesToTagOptions(workspace_ids=[args.workspace_id]),
98+
)
99+
print("[ADD_WORKSPACES] workspace associated")
100+
except TFEError as exc:
101+
print(f"[ADD_WORKSPACES] API error: {exc}")
102+
else:
103+
print("[ADD_WORKSPACES] skipped: set --workspace-id or TFE_WORKSPACE_ID")
104+
105+
# 3) Delete tag
106+
print("[DELETE] Deleting a tag from the organization")
107+
print(f"[DELETE] organization={args.org}, tag_id={args.tag_id}")
108+
try:
109+
client.organization_tags.delete(
110+
args.org,
111+
OrganizationTagsDeleteOptions(ids=[args.tag_id]),
112+
)
113+
print("[DELETE] tag deleted")
114+
except TFEError as exc:
115+
print(f"[DELETE] API error: {exc}")
116+
117+
118+
if __name__ == "__main__":
119+
main()

examples/run.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ def main():
4343
parser.add_argument(
4444
"--run-actions", action="store_true", help="Demo run actions (safe mode)"
4545
)
46+
parser.add_argument(
47+
"--invoke-action",
48+
metavar="ACTION_ADDR",
49+
help=(
50+
"Invoke a Terraform Action by its address, e.g. "
51+
"'action.aws_lambda_invoke.api_handler'. Requires --workspace-id."
52+
),
53+
)
4654
args = parser.parse_args()
4755

4856
if not args.token:
@@ -57,6 +65,10 @@ def main():
5765
print("Error: --create-run requires --workspace-id")
5866
return
5967

68+
if args.invoke_action and not args.workspace_id:
69+
print("Error: --invoke-action requires --workspace-id")
70+
return
71+
6072
cfg = TFEConfig(address=args.address, token=args.token)
6173
client = TFEClient(cfg)
6274

@@ -257,6 +269,32 @@ def main():
257269
print("\n Note: These actions are commented out for safety.")
258270
print("Uncomment and use them carefully in your own code.")
259271

272+
# 6) Invoke a Terraform Action
273+
if args.invoke_action and args.workspace_id:
274+
_print_header(f"Invoking Terraform Action: {args.invoke_action}")
275+
276+
try:
277+
workspace = Workspace(id=args.workspace_id)
278+
279+
create_options = RunCreateOptions(
280+
workspace=workspace,
281+
message=f"Invoking {args.invoke_action} via python-tfe SDK",
282+
invoke_action_addrs=[args.invoke_action],
283+
)
284+
285+
run = client.runs.create(create_options)
286+
287+
print(f"Run ID : {run.id}")
288+
print(f"Status : {run.status}")
289+
print(f"invoke-action-addrs: {run.invoke_action_addrs}")
290+
print(f"Message : {run.message}")
291+
292+
except Exception as e:
293+
print(f"Error invoking action: {e}")
294+
import traceback
295+
296+
traceback.print_exc()
297+
260298

261299
if __name__ == "__main__":
262300
main()

0 commit comments

Comments
 (0)