Skip to content

Commit dc409c7

Browse files
Merge branch 'next-1.0.0' into user-api
2 parents 93901a2 + 970fe63 commit dc409c7

8 files changed

Lines changed: 771 additions & 41 deletions

File tree

examples/team.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import os
5+
6+
from pytfe import TFEClient, TFEConfig
7+
from pytfe.models import (
8+
TeamCreateOptions,
9+
TeamIncludeOpt,
10+
TeamListOptions,
11+
TeamUpdateOptions,
12+
)
13+
14+
15+
def _print_header(title: str):
16+
print("\n" + "=" * 80)
17+
print(title)
18+
print("=" * 80)
19+
20+
21+
def main():
22+
parser = argparse.ArgumentParser(description="Teams list demo for python-tfe SDK")
23+
parser.add_argument(
24+
"--address", default=os.getenv("TFE_ADDRESS", "https://app.terraform.io")
25+
)
26+
parser.add_argument("--token", default=os.getenv("TFE_TOKEN", ""))
27+
parser.add_argument(
28+
"--org",
29+
required=True,
30+
help="Organization name",
31+
)
32+
parser.add_argument(
33+
"--page-size",
34+
type=int,
35+
default=20,
36+
help="Page size for fetching teams",
37+
)
38+
parser.add_argument(
39+
"--query",
40+
default=None,
41+
help="Optional q filter for team search",
42+
)
43+
parser.add_argument(
44+
"--names",
45+
nargs="+",
46+
default=None,
47+
help="Optional team names filter (space-separated)",
48+
)
49+
parser.add_argument(
50+
"--include-users",
51+
action="store_true",
52+
help="Include related users",
53+
)
54+
parser.add_argument(
55+
"--include-memberships",
56+
action="store_true",
57+
help="Include related organization-memberships",
58+
)
59+
parser.add_argument(
60+
"--create",
61+
action="store_true",
62+
help="Create a new team before listing",
63+
)
64+
parser.add_argument(
65+
"--name",
66+
default=None,
67+
help="Team name for create operation",
68+
)
69+
parser.add_argument(
70+
"--visibility",
71+
default="secret",
72+
help="Team visibility for create operation (secret or organization)",
73+
)
74+
parser.add_argument(
75+
"--sso-team-id",
76+
default=None,
77+
help="Optional SSO team ID for create operation",
78+
)
79+
parser.add_argument(
80+
"--allow-member-token-management",
81+
action="store_true",
82+
help="Enable member token management on create/update",
83+
)
84+
parser.add_argument(
85+
"--update",
86+
action="store_true",
87+
help="Update a team before listing",
88+
)
89+
parser.add_argument(
90+
"--read",
91+
action="store_true",
92+
help="Read a team by ID before listing",
93+
)
94+
parser.add_argument(
95+
"--delete",
96+
action="store_true",
97+
help="Delete a team by ID before listing",
98+
)
99+
parser.add_argument(
100+
"--team-id",
101+
default=None,
102+
help="Team ID for read/update/delete operation",
103+
)
104+
args = parser.parse_args()
105+
106+
cfg = TFEConfig(address=args.address, token=args.token)
107+
client = TFEClient(cfg)
108+
109+
if args.create:
110+
if not args.name:
111+
print("Error: --name is required when using --create")
112+
return
113+
114+
_print_header(f"Creating team in organization: {args.org}")
115+
create_options = TeamCreateOptions(
116+
name=args.name,
117+
visibility=args.visibility,
118+
sso_team_id=args.sso_team_id,
119+
allow_member_token_management=args.allow_member_token_management,
120+
)
121+
new_team = client.teams.create(args.org, create_options)
122+
print(f"Created Team ID: {new_team.id}")
123+
print(f"Name: {new_team.name}")
124+
print(f"Visibility: {new_team.visibility}")
125+
print(
126+
f"Allow Member Token Management: {new_team.allow_member_token_management}"
127+
)
128+
print()
129+
130+
if args.update:
131+
if not args.team_id:
132+
print("Error: --team-id is required when using --update")
133+
return
134+
135+
_print_header(f"Updating team: {args.team_id}")
136+
update_options = TeamUpdateOptions(
137+
name=args.name,
138+
visibility=args.visibility,
139+
sso_team_id=args.sso_team_id,
140+
allow_member_token_management=args.allow_member_token_management,
141+
)
142+
updated_team = client.teams.update(args.team_id, update_options)
143+
print(f"Updated Team ID: {updated_team.id}")
144+
print(f"Name: {updated_team.name}")
145+
print(f"Visibility: {updated_team.visibility}")
146+
print(
147+
f"Allow Member Token Management: {updated_team.allow_member_token_management}"
148+
)
149+
print()
150+
151+
if args.read:
152+
if not args.team_id:
153+
print("Error: --team-id is required when using --read")
154+
return
155+
156+
_print_header(f"Reading team: {args.team_id}")
157+
team = client.teams.read(args.team_id)
158+
print(f"Team ID: {team.id}")
159+
print(f"Name: {team.name}")
160+
print(f"Visibility: {team.visibility}")
161+
print(f"Is Unified: {team.is_unified}")
162+
print(f"User Count: {team.user_count}")
163+
print(f"Allow Member Token Management: {team.allow_member_token_management}")
164+
165+
if team.organization_access:
166+
print("Organization Access:")
167+
print(f" - manage_workspaces={team.organization_access.manage_workspaces}")
168+
print(f" - read_workspaces={team.organization_access.read_workspaces}")
169+
print(f" - manage_projects={team.organization_access.manage_projects}")
170+
171+
if team.permissions:
172+
print("Permissions:")
173+
print(f" - can_update_membership={team.permissions.can_update_membership}")
174+
print(f" - can_destroy={team.permissions.can_destroy}")
175+
176+
print(f"Users included: {len(team.users)}")
177+
print(
178+
f"Organization memberships included: {len(team.organization_memberships)}"
179+
)
180+
print()
181+
182+
if args.delete:
183+
if not args.team_id:
184+
print("Error: --team-id is required when using --delete")
185+
return
186+
187+
_print_header(f"Deleting team: {args.team_id}")
188+
client.teams.delete(args.team_id)
189+
print(f"Deleted Team ID: {args.team_id}")
190+
print()
191+
192+
includes: list[TeamIncludeOpt] = []
193+
if args.include_users:
194+
includes.append(TeamIncludeOpt.TEAM_USERS)
195+
if args.include_memberships:
196+
includes.append(TeamIncludeOpt.TEAM_ORGANIZATION_MEMBERSHIPS)
197+
198+
options = TeamListOptions(
199+
page_size=args.page_size,
200+
query=args.query,
201+
names=args.names,
202+
include=includes or None,
203+
)
204+
205+
_print_header(f"Listing teams for organization: {args.org}")
206+
print("Options:")
207+
print(f"- page_size={args.page_size}")
208+
print(f"- query={args.query}")
209+
print(f"- names={args.names}")
210+
print(f"- include={[item.value for item in includes] if includes else None}")
211+
print()
212+
213+
count = 0
214+
for team in client.teams.list(args.org, options):
215+
count += 1
216+
print(f"[{count}] Team ID: {team.id}")
217+
print(f"Name: {team.name}")
218+
print(f"Visibility: {team.visibility}")
219+
print(f"Is Unified: {team.is_unified}")
220+
print(f"User Count: {team.user_count}")
221+
print(f"Allow Member Token Management: {team.allow_member_token_management}")
222+
223+
if team.organization_access:
224+
print("Organization Access:")
225+
print(f" - manage_workspaces={team.organization_access.manage_workspaces}")
226+
print(f" - read_workspaces={team.organization_access.read_workspaces}")
227+
print(f" - manage_projects={team.organization_access.manage_projects}")
228+
229+
if team.permissions:
230+
print("Permissions:")
231+
print(f" - can_update_membership={team.permissions.can_update_membership}")
232+
print(f" - can_destroy={team.permissions.can_destroy}")
233+
234+
print(f"Users included: {len(team.users)}")
235+
print(
236+
f"Organization memberships included: {len(team.organization_memberships)}"
237+
)
238+
print()
239+
240+
if count == 0:
241+
print("No teams found.")
242+
else:
243+
print(f"Total teams: {count}")
244+
245+
246+
if __name__ == "__main__":
247+
main()

src/pytfe/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
from .resources.ssh_keys import SSHKeys
3636
from .resources.state_version_outputs import StateVersionOutputs
3737
from .resources.state_versions import StateVersions
38+
3839
from .resources.user import Users
40+
from .resources.team import Teams
41+
3942
from .resources.variable import Variables
4043
from .resources.variable_sets import VariableSets, VariableSetVariables
4144
from .resources.workspace_resources import WorkspaceResourcesService
@@ -102,6 +105,7 @@ def __init__(self, config: TFEConfig | None = None):
102105

103106
# SSH Keys
104107
self.ssh_keys = SSHKeys(self._transport)
108+
self.teams = Teams(self._transport)
105109

106110
# Reserved Tag Key
107111
self.reserved_tag_key = ReservedTagKeys(self._transport)

src/pytfe/errors.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,3 +530,18 @@ class InvalidKeyIDError(InvalidValues):
530530

531531
def __init__(self, message: str = "invalid value for key-id"):
532532
super().__init__(message)
533+
534+
535+
# Team errors
536+
class EmptyTeamNameError(InvalidValues):
537+
"""Raised when a team name is empty."""
538+
539+
def __init__(self, message: str = "team names cannot be empty"):
540+
super().__init__(message)
541+
542+
543+
class InvalidTeamIDError(InvalidValues):
544+
"""Raised when an invalid team ID is provided."""
545+
546+
def __init__(self, message: str = "invalid value for team ID"):
547+
super().__init__(message)

src/pytfe/models/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,11 @@
307307
from .team import (
308308
OrganizationAccess,
309309
Team,
310+
TeamCreateOptions,
311+
TeamIncludeOpt,
312+
TeamListOptions,
310313
TeamPermissions,
314+
TeamUpdateOptions,
311315
)
312316

313317
# Variables
@@ -500,6 +504,10 @@
500504
"OrganizationAccess",
501505
"Team",
502506
"TeamPermissions",
507+
"TeamCreateOptions",
508+
"TeamIncludeOpt",
509+
"TeamListOptions",
510+
"TeamUpdateOptions",
503511
"Project",
504512
"ProjectAddTagBindingsOptions",
505513
"ProjectCreateOptions",

src/pytfe/models/organization_membership.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class OrganizationMembership(BaseModel):
3434
model_config = ConfigDict(populate_by_name=True)
3535

3636
id: str
37-
status: OrganizationMembershipStatus
38-
email: str
37+
status: OrganizationMembershipStatus | None = Field(default=None, alias="status")
38+
email: str = Field(default="", alias="email")
3939

4040
# Relations
4141
organization: Organization | None = None

0 commit comments

Comments
 (0)