Skip to content

Commit 0589a1b

Browse files
authored
Merge pull request #39 from hashicorp/ReservedTagKey
Python TFE - Reserved Tag Key implementation
2 parents 0b2afd2 + 6a863d1 commit 0589a1b

7 files changed

Lines changed: 492 additions & 0 deletions

File tree

examples/reserved_tag_key.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
"""Reserved Tag Keys Example Script.
3+
4+
This script demonstrates how to use the Reserved Tag Keys API to:
5+
1. List all reserved tag keys for an organization
6+
2. Create a new reserved tag key
7+
3. Update a reserved tag key
8+
4. Delete a reserved tag key
9+
10+
Before running this script:
11+
1. Set TFE_TOKEN environment variable with your Terraform Cloud API token
12+
2. Set TFE_ORG environment variable with your organization name
13+
"""
14+
15+
import os
16+
import sys
17+
18+
# Add the source directory to the path for direct execution
19+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
20+
21+
from tfe import TFEClient, TFEConfig
22+
from tfe.errors import TFEError
23+
from tfe.models import (
24+
ReservedTagKeyCreateOptions,
25+
ReservedTagKeyListOptions,
26+
ReservedTagKeyUpdateOptions,
27+
)
28+
29+
# Configuration
30+
TFE_TOKEN = os.getenv("TFE_TOKEN")
31+
TFE_ORG = os.getenv("TFE_ORG")
32+
33+
34+
def main():
35+
"""Main function demonstrating Reserved Tag Keys API usage."""
36+
37+
# Validate environment variables
38+
if not TFE_TOKEN:
39+
print("❌ Error: TFE_TOKEN environment variable is required")
40+
sys.exit(1)
41+
42+
if not TFE_ORG:
43+
print("❌ Error: TFE_ORG environment variable is required")
44+
sys.exit(1)
45+
46+
# Initialize the TFE client
47+
config = TFEConfig(token=TFE_TOKEN)
48+
client = TFEClient(config)
49+
50+
print(f"Reserved Tag Keys API Example for organization: {TFE_ORG}")
51+
print("=" * 60)
52+
53+
try:
54+
# 1. List existing reserved tag keys
55+
print("\n1. Listing reserved tag keys...")
56+
reserved_tag_keys = client.reserved_tag_key.list(TFE_ORG)
57+
print(f"✅ Found {len(reserved_tag_keys.items)} reserved tag keys:")
58+
for rtk in reserved_tag_keys.items:
59+
print(
60+
f" - ID: {rtk.id}, Key: {rtk.key}, Disable Overrides: {rtk.disable_overrides}"
61+
)
62+
63+
# 2. Create a new reserved tag key
64+
print("\n2. Creating a new reserved tag key...")
65+
create_options = ReservedTagKeyCreateOptions(
66+
key="python-tfe-example", disable_overrides=False
67+
)
68+
69+
new_rtk = client.reserved_tag_key.create(TFE_ORG, create_options)
70+
print(f"✅ Created reserved tag key: {new_rtk.id} - {new_rtk.key}")
71+
print(f" Disable Overrides: {new_rtk.disable_overrides}")
72+
73+
# 3. Update the reserved tag key
74+
print("\n3. Updating the reserved tag key...")
75+
update_options = ReservedTagKeyUpdateOptions(
76+
key="python-tfe-example-updated", disable_overrides=True
77+
)
78+
79+
updated_rtk = client.reserved_tag_key.update(new_rtk.id, update_options)
80+
print(f"✅ Updated reserved tag key: {updated_rtk.id} - {updated_rtk.key}")
81+
print(f" Disable Overrides: {updated_rtk.disable_overrides}")
82+
83+
# 4. Delete the reserved tag key
84+
print("\n4. Deleting the reserved tag key...")
85+
client.reserved_tag_key.delete(new_rtk.id)
86+
print(f"✅ Deleted reserved tag key: {new_rtk.id}")
87+
88+
# 5. Verify deletion by listing again
89+
print("\n5. Verifying deletion...")
90+
reserved_tag_keys_after = client.reserved_tag_key.list(TFE_ORG)
91+
print(
92+
f"✅ Reserved tag keys after deletion: {len(reserved_tag_keys_after.items)}"
93+
)
94+
95+
# 6. Demonstrate pagination with options
96+
print("\n6. Demonstrating pagination options...")
97+
list_options = ReservedTagKeyListOptions(page_size=5, page_number=1)
98+
paginated_rtks = client.reserved_tag_key.list(TFE_ORG, list_options)
99+
print(f"✅ Page 1 with page size 5: {len(paginated_rtks.items)} keys")
100+
print(f" Total pages: {paginated_rtks.total_pages}")
101+
print(f" Total count: {paginated_rtks.total_count}")
102+
103+
print("\n🎉 Reserved Tag Keys API example completed successfully!")
104+
105+
except NotImplementedError as e:
106+
print(f"\n⚠️ Note: {e}")
107+
print("This is expected - the read operation is not supported by the API.")
108+
109+
except TFEError as e:
110+
print(f"\n❌ TFE API Error: {e}")
111+
if hasattr(e, "status"):
112+
if e.status == 403:
113+
print("💡 Permission denied - check token permissions")
114+
elif e.status == 401:
115+
print("💡 Authentication failed - check token validity")
116+
elif e.status == 422:
117+
print("💡 Validation error - check reserved tag key format")
118+
sys.exit(1)
119+
120+
except Exception as e:
121+
print(f"\n❌ Unexpected error: {e}")
122+
sys.exit(1)
123+
124+
125+
if __name__ == "__main__":
126+
main()

src/tfe/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .resources.query_run import QueryRuns
1414
from .resources.registry_module import RegistryModules
1515
from .resources.registry_provider import RegistryProviders
16+
from .resources.reserved_tag_key import ReservedTagKey
1617
from .resources.run import Runs
1718
from .resources.run_event import RunEvents
1819
from .resources.run_task import RunTasks
@@ -73,5 +74,8 @@ def __init__(self, config: TFEConfig | None = None):
7374
# SSH Keys
7475
self.ssh_keys = SSHKeys(self._transport)
7576

77+
# Reserved Tag Key
78+
self.reserved_tag_key = ReservedTagKey(self._transport)
79+
7680
def close(self) -> None:
7781
pass

src/tfe/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ class ErrStateVersionUploadNotSupported(TFEError): ...
102102
# SSH Key Error Constants
103103
ERR_INVALID_SSH_KEY_ID = "invalid SSH key ID"
104104

105+
# Reserved Tag Key Error Constants
106+
ERR_INVALID_RESERVED_TAG_KEY_ID = "invalid reserved tag key ID"
107+
ERR_REQUIRED_TAG_KEY = "tag key is required"
108+
ERR_INVALID_TAG_KEY = "invalid tag key"
109+
105110

106111
class WorkspaceNotFound(NotFound): ...
107112

src/tfe/models/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@
111111
RegistryProviderReadOptions,
112112
)
113113

114+
# Re-export all reserved tag key types
115+
from .reserved_tag_key import (
116+
ReservedTagKey,
117+
ReservedTagKeyCreateOptions,
118+
ReservedTagKeyList,
119+
ReservedTagKeyListOptions,
120+
ReservedTagKeyUpdateOptions,
121+
)
122+
114123
# Re-export all SSH key types
115124
from .ssh_key import (
116125
SSHKey,
@@ -139,6 +148,12 @@
139148
"SSHKeyList",
140149
"SSHKeyListOptions",
141150
"SSHKeyUpdateOptions",
151+
# Reserved tag key types
152+
"ReservedTagKey",
153+
"ReservedTagKeyCreateOptions",
154+
"ReservedTagKeyList",
155+
"ReservedTagKeyListOptions",
156+
"ReservedTagKeyUpdateOptions",
142157
# Agent and agent pool types
143158
"Agent",
144159
"AgentPool",

src/tfe/models/reserved_tag_key.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
8+
class ReservedTagKey(BaseModel):
9+
"""Represents a reserved tag key in Terraform Enterprise."""
10+
11+
model_config = ConfigDict(populate_by_name=True)
12+
13+
id: str = Field(..., description="The unique identifier for this reserved tag key")
14+
type: str = Field(
15+
default="reserved-tag-keys", description="The type of this resource"
16+
)
17+
key: str = Field(..., description="The key targeted by this reserved tag key")
18+
disable_overrides: bool = Field(
19+
...,
20+
alias="disable-overrides",
21+
description="If true, disables overriding inherited tags with the specified key at the workspace level",
22+
)
23+
created_at: datetime | None = Field(
24+
None,
25+
alias="created-at",
26+
description="The time when the reserved tag key was created",
27+
)
28+
updated_at: datetime | None = Field(
29+
None,
30+
alias="updated-at",
31+
description="The time when the reserved tag key was last updated",
32+
)
33+
34+
35+
class ReservedTagKeyCreateOptions(BaseModel):
36+
"""Options for creating a new reserved tag key."""
37+
38+
model_config = ConfigDict(populate_by_name=True)
39+
40+
key: str = Field(..., description="The key targeted by this reserved tag key")
41+
disable_overrides: bool = Field(
42+
...,
43+
alias="disable-overrides",
44+
description="If true, disables overriding inherited tags with the specified key at the workspace level",
45+
)
46+
47+
48+
class ReservedTagKeyUpdateOptions(BaseModel):
49+
"""Options for updating a reserved tag key."""
50+
51+
model_config = ConfigDict(populate_by_name=True)
52+
53+
key: str | None = Field(
54+
None, description="The key targeted by this reserved tag key"
55+
)
56+
disable_overrides: bool | None = Field(
57+
None,
58+
alias="disable-overrides",
59+
description="If true, disables overriding inherited tags with the specified key at the workspace level",
60+
)
61+
62+
63+
class ReservedTagKeyListOptions(BaseModel):
64+
"""Options for listing reserved tag keys."""
65+
66+
model_config = ConfigDict(populate_by_name=True)
67+
68+
page_number: int | None = Field(
69+
None, alias="page[number]", description="Page number to retrieve", ge=1
70+
)
71+
page_size: int | None = Field(
72+
None, alias="page[size]", description="Number of items per page", ge=1, le=100
73+
)
74+
75+
76+
class ReservedTagKeyList(BaseModel):
77+
"""Represents a paginated list of reserved tag keys."""
78+
79+
model_config = ConfigDict(populate_by_name=True)
80+
81+
items: list[ReservedTagKey] = Field(
82+
default_factory=list, description="List of reserved tag keys"
83+
)
84+
current_page: int | None = Field(None, description="Current page number")
85+
total_pages: int | None = Field(None, description="Total number of pages")
86+
prev_page: str | None = Field(None, description="URL of the previous page")
87+
next_page: str | None = Field(None, description="URL of the next page")
88+
total_count: int | None = Field(None, description="Total number of items")

0 commit comments

Comments
 (0)