Skip to content

Commit d420fd2

Browse files
Add organization token support with models, resources, examples, and tests
1 parent 926bc7a commit d420fd2

5 files changed

Lines changed: 820 additions & 9 deletions

File tree

examples/organization_token.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Organization Token Operations Example
4+
5+
Demonstrates usage of all 6 organization token operations:
6+
1. create() - Create a new organization token, replacing any existing token
7+
2. create_with_options() - Create with options like expiration date and token type
8+
3. read() - Read the organization token
9+
4. read_with_options() - Read with options like token type
10+
5. delete() - Delete the organization token
11+
6. delete_with_options() - Delete with options like token type
12+
13+
Usage:
14+
- Modify organization names as needed for your environment
15+
- Ensure you have proper TFE credentials and organization access
16+
- Organization tokens are used for organization-level API access
17+
18+
Prerequisites:
19+
- Set TFE_TOKEN and TFE_ADDRESS environment variables
20+
- You need an existing organization or admin permissions to create one
21+
- Appropriate permissions to manage organization tokens
22+
"""
23+
24+
from datetime import datetime, timedelta
25+
26+
# Add the src directory to the path
27+
##sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
28+
from pytfe import TFEClient, TFEConfig
29+
from pytfe.models import (
30+
OrganizationTokenCreateOptions,
31+
OrganizationTokenDeleteOptions,
32+
OrganizationTokenReadOptions,
33+
TokenType,
34+
)
35+
36+
37+
def redact_token(token_value: str | None) -> str:
38+
"""Redact token value for safe display."""
39+
if not token_value:
40+
return "None"
41+
if len(token_value) <= 8:
42+
return f"{'*' * len(token_value)}"
43+
# Show first 3 and last 3 characters
44+
return f"{token_value[:3]}...{token_value[-3:]}".replace(
45+
token_value[3:-3], "*" * (len(token_value) - 6)
46+
)
47+
48+
49+
def redact_id(id_value: str | None) -> str:
50+
"""Redact ID for safe display."""
51+
if not id_value:
52+
return "None"
53+
if len(id_value) <= 6:
54+
return f"{'*' * len(id_value)}"
55+
# Show first 3 and last 3 characters
56+
return f"{id_value[:3]}...{id_value[-3:]}"
57+
58+
59+
def main():
60+
"""Execute organization token operation examples."""
61+
62+
print("=" * 80)
63+
print("ORGANIZATION TOKEN OPERATIONS")
64+
print("=" * 80)
65+
66+
# Initialize the TFE client
67+
client = TFEClient(TFEConfig.from_env())
68+
organization_name = "prab-sandbox02"
69+
# =====================================================
70+
# 1. CREATE ORGANIZATION TOKEN (BASIC)
71+
# =====================================================
72+
print("\n1. create() - Create a new organization token:")
73+
print("-" * 40)
74+
try:
75+
print(f"Creating token for organization: {organization_name}")
76+
token = client.organization_tokens.create(organization_name)
77+
78+
print("Token created successfully!")
79+
print(f" Token ID: {redact_id(token.id)}")
80+
print(f" Created At: {token.created_at}")
81+
print(f" Description: {token.description}")
82+
print(f" Token Value: {redact_token(token.token)}")
83+
if token.expired_at:
84+
print(f" Expires At: {token.expired_at}")
85+
print()
86+
87+
except Exception as e:
88+
print(f" Error: {e}")
89+
print()
90+
91+
# =====================================================
92+
# 2. CREATE WITH OPTIONS (WITH EXPIRATION)
93+
# =====================================================
94+
print("2. create_with_options() - Create token with expiration date:")
95+
print("-" * 40)
96+
try:
97+
# Create a token that expires in 30 days
98+
expiry_date = datetime.utcnow() + timedelta(days=30)
99+
options = OrganizationTokenCreateOptions(expired_at=expiry_date)
100+
101+
print(f"Creating organization token with expiration date: {expiry_date}")
102+
token = client.organization_tokens.create_with_options(
103+
organization_name, options
104+
)
105+
106+
print("Token created with options successfully!")
107+
print(f" Token ID: {redact_id(token.id)}")
108+
print(f" Created At: {token.created_at}")
109+
if token.expired_at:
110+
print(f" Expires At: {token.expired_at}")
111+
print()
112+
113+
except Exception as e:
114+
print(f" Error: {e}")
115+
print()
116+
117+
# =====================================================
118+
print("3. create_with_options() - Create audit-trails token:")
119+
print("-" * 40)
120+
try:
121+
options = OrganizationTokenCreateOptions(token_type=TokenType.AUDIT_TRAILS)
122+
123+
print(f"Creating audit-trails token for organization: {organization_name}")
124+
token = client.organization_tokens.create_with_options(
125+
organization_name, options
126+
)
127+
128+
print(" Audit-trails token created successfully!")
129+
print(f" Token ID: {redact_id(token.id)}")
130+
print(f" Token Value: {redact_token(token.token)}")
131+
print()
132+
133+
except Exception as e:
134+
print(f"Error: {e}")
135+
print()
136+
137+
# =====================================================
138+
print("4. read() - Read the organization token:")
139+
print("-" * 40)
140+
try:
141+
print(f"Reading organization token for organization: {organization_name}")
142+
token = client.organization_tokens.read(organization_name)
143+
144+
print("Token read successfully!")
145+
print(f" Token ID: {redact_id(token.id)}")
146+
print(f" Created At: {token.created_at}")
147+
print(f" Description: {token.description}")
148+
if token.last_used_at:
149+
print(f" Last Used At: {token.last_used_at}")
150+
if token.expired_at:
151+
print(f" Expires At: {token.expired_at}")
152+
print()
153+
154+
except Exception as e:
155+
print(f" Error: {e}")
156+
print()
157+
158+
# =====================================================
159+
print("5. read_with_options() - Read audit-trails token:")
160+
print("-" * 40)
161+
try:
162+
options = OrganizationTokenReadOptions(token_type=TokenType.AUDIT_TRAILS)
163+
164+
print(f"Reading audit-trails token for organization: {organization_name}")
165+
token = client.organization_tokens.read_with_options(organization_name, options)
166+
167+
print(" Audit-trails token read successfully!")
168+
print(f" Token ID: {redact_id(token.id)}")
169+
print(f" Token Value: {redact_token(token.token)}")
170+
print()
171+
172+
except Exception as e:
173+
print(f" Error: {e}")
174+
print()
175+
176+
# =====================================================
177+
print("6. delete() - Delete the organization token:")
178+
print("-" * 40)
179+
try:
180+
print(f"Deleting organization token for organization: {organization_name}")
181+
client.organization_tokens.delete(organization_name)
182+
183+
print(" Token deleted successfully!")
184+
print()
185+
186+
except Exception as e:
187+
print(f" Error: {e}")
188+
print()
189+
190+
# =====================================================
191+
print("7. delete_with_options() - Delete audit-trails token:")
192+
print("-" * 40)
193+
try:
194+
options = OrganizationTokenDeleteOptions(token_type=TokenType.AUDIT_TRAILS)
195+
196+
print(f"Deleting audit-trails token for organization: {organization_name}")
197+
client.organization_tokens.delete_with_options(organization_name, options)
198+
199+
print(" Audit-trails token deleted successfully!")
200+
print()
201+
202+
except Exception as e:
203+
print(f"Error: {e}")
204+
print()
205+
206+
print("=" * 80)
207+
print("ORGANIZATION TOKEN OPERATIONS COMPLETED")
208+
print("=" * 80)
209+
210+
211+
if __name__ == "__main__":
212+
main()

src/pytfe/client.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .resources.oauth_client import OAuthClients
1414
from .resources.oauth_token import OAuthTokens
1515
from .resources.organization_membership import OrganizationMemberships
16+
from .resources.organization_token import OrganizationTokens
1617
from .resources.organizations import Organizations
1718
from .resources.plan import Plans
1819
from .resources.policy import Policies
@@ -33,11 +34,8 @@
3334
from .resources.run_task import RunTasks
3435
from .resources.run_trigger import RunTriggers
3536
from .resources.ssh_keys import SSHKeys
36-
from .resources.stack import Stacks
3737
from .resources.state_version_outputs import StateVersionOutputs
3838
from .resources.state_versions import StateVersions
39-
from .resources.team import Teams
40-
from .resources.team_project_access import TeamProjectAccesses
4139
from .resources.variable import Variables
4240
from .resources.variable_sets import VariableSets, VariableSetVariables
4341
from .resources.workspace_resources import WorkspaceResourcesService
@@ -75,7 +73,7 @@ def __init__(self, config: TFEConfig | None = None):
7573
self.plans = Plans(self._transport)
7674
self.organizations = Organizations(self._transport)
7775
self.organization_memberships = OrganizationMemberships(self._transport)
78-
76+
self.organization_tokens = OrganizationTokens(self._transport)
7977
self.projects = Projects(self._transport)
8078
self.variables = Variables(self._transport)
8179
self.variable_sets = VariableSets(self._transport)
@@ -104,16 +102,12 @@ def __init__(self, config: TFEConfig | None = None):
104102

105103
# SSH Keys
106104
self.ssh_keys = SSHKeys(self._transport)
107-
# Team project access
108-
self.team_project_accesses = TeamProjectAccesses(self._transport)
109-
self.teams = Teams(self._transport)
110105

111106
# Reserved Tag Key
112107
self.reserved_tag_key = ReservedTagKeys(self._transport)
113-
self.stacks = Stacks(self._transport)
114108

115109
def close(self) -> None:
116110
try:
117111
self._transport._sync.close()
118112
except Exception:
119-
pass
113+
pass
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime
4+
from enum import Enum
5+
from typing import TYPE_CHECKING, Any
6+
7+
from pydantic import BaseModel, ConfigDict, Field
8+
9+
if TYPE_CHECKING:
10+
pass
11+
12+
13+
class TokenType(str, Enum):
14+
"""Token type enumeration."""
15+
16+
AUDIT_TRAILS = "audit-trails"
17+
18+
19+
class OrganizationToken(BaseModel):
20+
"""Organization token represents a Terraform Enterprise organization token."""
21+
22+
model_config = ConfigDict(extra="forbid")
23+
24+
id: str = Field(..., description="Organization token ID")
25+
created_at: datetime = Field(..., description="Creation timestamp")
26+
description: str | None = Field(None, description="Token description")
27+
last_used_at: datetime | None = Field(None, description="Last usage timestamp")
28+
token: str | None = Field(None, description="The actual token value")
29+
expired_at: datetime | None = Field(None, description="Token expiration timestamp")
30+
created_by: Any | None = Field(
31+
None, description="The entity that created this token"
32+
)
33+
34+
35+
class OrganizationTokenCreateOptions(BaseModel):
36+
"""Options for creating an organization token."""
37+
38+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
39+
40+
expired_at: datetime | None = Field(
41+
None,
42+
description="The token's expiration date. Available in TFE release v202305-1 and later",
43+
)
44+
token_type: TokenType | None = Field(
45+
None,
46+
alias="token",
47+
description="What type of token to create. Only applicable to HCP Terraform",
48+
)
49+
50+
51+
class OrganizationTokenReadOptions(BaseModel):
52+
"""Options for reading an organization token."""
53+
54+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
55+
56+
token_type: TokenType | None = Field(
57+
None,
58+
alias="token",
59+
description="What type of token to read. Only applicable to HCP Terraform",
60+
)
61+
62+
63+
class OrganizationTokenDeleteOptions(BaseModel):
64+
"""Options for deleting an organization token."""
65+
66+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
67+
68+
token_type: TokenType | None = Field(
69+
None,
70+
alias="token",
71+
description="What type of token to delete. Only applicable to HCP Terraform",
72+
)

0 commit comments

Comments
 (0)