bt-cli 0.4.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. bt_cli/__init__.py +3 -0
  2. bt_cli/cli.py +830 -0
  3. bt_cli/commands/__init__.py +1 -0
  4. bt_cli/commands/configure.py +415 -0
  5. bt_cli/commands/learn.py +229 -0
  6. bt_cli/commands/quick.py +784 -0
  7. bt_cli/core/__init__.py +1 -0
  8. bt_cli/core/auth.py +213 -0
  9. bt_cli/core/client.py +313 -0
  10. bt_cli/core/config.py +393 -0
  11. bt_cli/core/config_file.py +420 -0
  12. bt_cli/core/csv_utils.py +91 -0
  13. bt_cli/core/errors.py +247 -0
  14. bt_cli/core/output.py +205 -0
  15. bt_cli/core/prompts.py +87 -0
  16. bt_cli/core/rest_debug.py +221 -0
  17. bt_cli/data/CLAUDE.md +94 -0
  18. bt_cli/data/__init__.py +0 -0
  19. bt_cli/data/skills/bt/SKILL.md +108 -0
  20. bt_cli/data/skills/entitle/SKILL.md +170 -0
  21. bt_cli/data/skills/epmw/SKILL.md +144 -0
  22. bt_cli/data/skills/pra/SKILL.md +150 -0
  23. bt_cli/data/skills/pws/SKILL.md +198 -0
  24. bt_cli/entitle/__init__.py +1 -0
  25. bt_cli/entitle/client/__init__.py +5 -0
  26. bt_cli/entitle/client/base.py +443 -0
  27. bt_cli/entitle/commands/__init__.py +24 -0
  28. bt_cli/entitle/commands/accounts.py +53 -0
  29. bt_cli/entitle/commands/applications.py +39 -0
  30. bt_cli/entitle/commands/auth.py +68 -0
  31. bt_cli/entitle/commands/bundles.py +218 -0
  32. bt_cli/entitle/commands/integrations.py +60 -0
  33. bt_cli/entitle/commands/permissions.py +70 -0
  34. bt_cli/entitle/commands/policies.py +97 -0
  35. bt_cli/entitle/commands/resources.py +131 -0
  36. bt_cli/entitle/commands/roles.py +74 -0
  37. bt_cli/entitle/commands/users.py +123 -0
  38. bt_cli/entitle/commands/workflows.py +187 -0
  39. bt_cli/entitle/models/__init__.py +31 -0
  40. bt_cli/entitle/models/bundle.py +28 -0
  41. bt_cli/entitle/models/common.py +37 -0
  42. bt_cli/entitle/models/integration.py +30 -0
  43. bt_cli/entitle/models/permission.py +27 -0
  44. bt_cli/entitle/models/policy.py +25 -0
  45. bt_cli/entitle/models/resource.py +29 -0
  46. bt_cli/entitle/models/role.py +28 -0
  47. bt_cli/entitle/models/user.py +24 -0
  48. bt_cli/entitle/models/workflow.py +55 -0
  49. bt_cli/epmw/__init__.py +1 -0
  50. bt_cli/epmw/client/__init__.py +5 -0
  51. bt_cli/epmw/client/base.py +848 -0
  52. bt_cli/epmw/commands/__init__.py +33 -0
  53. bt_cli/epmw/commands/audits.py +250 -0
  54. bt_cli/epmw/commands/auth.py +55 -0
  55. bt_cli/epmw/commands/computers.py +140 -0
  56. bt_cli/epmw/commands/events.py +233 -0
  57. bt_cli/epmw/commands/groups.py +215 -0
  58. bt_cli/epmw/commands/policies.py +673 -0
  59. bt_cli/epmw/commands/quick.py +348 -0
  60. bt_cli/epmw/commands/requests.py +224 -0
  61. bt_cli/epmw/commands/roles.py +78 -0
  62. bt_cli/epmw/commands/tasks.py +38 -0
  63. bt_cli/epmw/commands/users.py +219 -0
  64. bt_cli/epmw/models/__init__.py +1 -0
  65. bt_cli/pra/__init__.py +1 -0
  66. bt_cli/pra/client/__init__.py +5 -0
  67. bt_cli/pra/client/base.py +618 -0
  68. bt_cli/pra/commands/__init__.py +30 -0
  69. bt_cli/pra/commands/auth.py +55 -0
  70. bt_cli/pra/commands/import_export.py +442 -0
  71. bt_cli/pra/commands/jump_clients.py +139 -0
  72. bt_cli/pra/commands/jump_groups.py +146 -0
  73. bt_cli/pra/commands/jump_items.py +638 -0
  74. bt_cli/pra/commands/jumpoints.py +95 -0
  75. bt_cli/pra/commands/policies.py +197 -0
  76. bt_cli/pra/commands/quick.py +470 -0
  77. bt_cli/pra/commands/teams.py +81 -0
  78. bt_cli/pra/commands/users.py +87 -0
  79. bt_cli/pra/commands/vault.py +564 -0
  80. bt_cli/pra/models/__init__.py +27 -0
  81. bt_cli/pra/models/common.py +12 -0
  82. bt_cli/pra/models/jump_client.py +25 -0
  83. bt_cli/pra/models/jump_group.py +15 -0
  84. bt_cli/pra/models/jump_item.py +72 -0
  85. bt_cli/pra/models/jumpoint.py +19 -0
  86. bt_cli/pra/models/team.py +14 -0
  87. bt_cli/pra/models/user.py +17 -0
  88. bt_cli/pra/models/vault.py +45 -0
  89. bt_cli/pws/__init__.py +1 -0
  90. bt_cli/pws/client/__init__.py +5 -0
  91. bt_cli/pws/client/base.py +356 -0
  92. bt_cli/pws/client/beyondinsight.py +869 -0
  93. bt_cli/pws/client/passwordsafe.py +1786 -0
  94. bt_cli/pws/commands/__init__.py +33 -0
  95. bt_cli/pws/commands/accounts.py +372 -0
  96. bt_cli/pws/commands/assets.py +311 -0
  97. bt_cli/pws/commands/auth.py +166 -0
  98. bt_cli/pws/commands/clouds.py +221 -0
  99. bt_cli/pws/commands/config.py +344 -0
  100. bt_cli/pws/commands/credentials.py +347 -0
  101. bt_cli/pws/commands/databases.py +306 -0
  102. bt_cli/pws/commands/directories.py +199 -0
  103. bt_cli/pws/commands/functional.py +298 -0
  104. bt_cli/pws/commands/import_export.py +452 -0
  105. bt_cli/pws/commands/platforms.py +118 -0
  106. bt_cli/pws/commands/quick.py +1646 -0
  107. bt_cli/pws/commands/search.py +256 -0
  108. bt_cli/pws/commands/secrets.py +1343 -0
  109. bt_cli/pws/commands/systems.py +389 -0
  110. bt_cli/pws/commands/users.py +415 -0
  111. bt_cli/pws/commands/workgroups.py +166 -0
  112. bt_cli/pws/config.py +18 -0
  113. bt_cli/pws/models/__init__.py +19 -0
  114. bt_cli/pws/models/account.py +186 -0
  115. bt_cli/pws/models/asset.py +102 -0
  116. bt_cli/pws/models/common.py +132 -0
  117. bt_cli/pws/models/system.py +121 -0
  118. bt_cli-0.4.13.dist-info/METADATA +417 -0
  119. bt_cli-0.4.13.dist-info/RECORD +121 -0
  120. bt_cli-0.4.13.dist-info/WHEEL +4 -0
  121. bt_cli-0.4.13.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,123 @@
1
+ """User commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage users")
12
+
13
+
14
+ @app.command("list")
15
+ def list_users(
16
+ search: Optional[str] = typer.Option(None, "--search", "-s", help="Search by email or name"),
17
+ limit: int = typer.Option(100, "--limit", "-l", help="Maximum results to return"),
18
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
19
+ ) -> None:
20
+ """List users in Entitle.
21
+
22
+ Examples:
23
+ bt entitle users list
24
+ bt entitle users list -s "john"
25
+ bt entitle users list -l 50 -o json
26
+ """
27
+ try:
28
+ with get_client() as client:
29
+ data = client.list_users(search=search, limit=limit)
30
+
31
+ if output == "json":
32
+ print_json(data)
33
+ else:
34
+ # Format for display - combine givenName/familyName, format date
35
+ display_data = []
36
+ for user in data:
37
+ given = user.get("givenName", "") or ""
38
+ family = user.get("familyName", "") or ""
39
+ name = f"{given} {family}".strip() or "-"
40
+ created = user.get("createdAt", "")
41
+ if created:
42
+ created = created[:10] # Just the date part
43
+ display_data.append({
44
+ "id": user.get("id"),
45
+ "email": user.get("email"),
46
+ "name": name,
47
+ "created": created,
48
+ })
49
+
50
+ print_table(
51
+ display_data,
52
+ [("ID", "id"), ("Email", "email"), ("Name", "name"), ("Created", "created")],
53
+ title="Users",
54
+ )
55
+ except httpx.HTTPStatusError as e:
56
+ print_api_error(e, "list users")
57
+ raise typer.Exit(1)
58
+ except httpx.RequestError as e:
59
+ print_api_error(e, "list users")
60
+ raise typer.Exit(1)
61
+ except Exception as e:
62
+ print_api_error(e, "list users")
63
+ raise typer.Exit(1)
64
+
65
+
66
+ @app.command("get")
67
+ def get_user(
68
+ search: str = typer.Argument(..., help="User email or name to search for"),
69
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
70
+ ) -> None:
71
+ """Find and display a user by email or name.
72
+
73
+ Note: The Entitle API doesn't support getting users by ID directly.
74
+ This command searches for users matching the provided email or name.
75
+
76
+ Examples:
77
+ bt entitle users get john@example.com
78
+ bt entitle users get "John Smith"
79
+ """
80
+ try:
81
+ with get_client() as client:
82
+ data = client.list_users(search=search, limit=10)
83
+
84
+ if not data:
85
+ console.print(f"[yellow]No users found matching '{search}'[/yellow]")
86
+ raise typer.Exit(1)
87
+
88
+ if output == "json":
89
+ print_json(data)
90
+ else:
91
+ # Show matching users
92
+ display_data = []
93
+ for user in data:
94
+ given = user.get("givenName", "") or ""
95
+ family = user.get("familyName", "") or ""
96
+ name = f"{given} {family}".strip() or "-"
97
+ created = user.get("createdAt", "")
98
+ if created:
99
+ created = created[:10]
100
+ display_data.append({
101
+ "id": user.get("id"),
102
+ "email": user.get("email"),
103
+ "name": name,
104
+ "created": created,
105
+ })
106
+
107
+ print_table(
108
+ display_data,
109
+ [("ID", "id"), ("Email", "email"), ("Name", "name"), ("Created", "created")],
110
+ title=f"Users matching '{search}'",
111
+ )
112
+
113
+ except httpx.HTTPStatusError as e:
114
+ print_api_error(e, "find user")
115
+ raise typer.Exit(1)
116
+ except httpx.RequestError as e:
117
+ print_api_error(e, "find user")
118
+ raise typer.Exit(1)
119
+ except typer.Exit:
120
+ raise
121
+ except Exception as e:
122
+ print_api_error(e, "find user")
123
+ raise typer.Exit(1)
@@ -0,0 +1,187 @@
1
+ """Workflow commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage workflows")
12
+
13
+
14
+ def _summarize_workflow(workflow: dict) -> str:
15
+ """Generate a short description of a workflow based on its rules."""
16
+ rules = workflow.get("rules", [])
17
+ if not rules:
18
+ return "No rules defined"
19
+
20
+ # Check first rule's approval type
21
+ first_rule = rules[0]
22
+ approval_flow = first_rule.get("approvalFlow", {})
23
+ steps = approval_flow.get("steps", [])
24
+
25
+ if not steps:
26
+ return "No approval steps"
27
+
28
+ # Check if auto-approve
29
+ first_step = steps[0]
30
+ entities = first_step.get("approvalEntities", [])
31
+ if entities and entities[0].get("type") == "Automatic":
32
+ duration = first_rule.get("underDuration", 0)
33
+ hours = duration // 3600
34
+ return f"Auto-approve (≤{hours}h)"
35
+
36
+ # Count steps and approvers
37
+ if len(steps) == 1:
38
+ return f"Single approver ({len(entities)} options)"
39
+ else:
40
+ return f"Multi-step ({len(steps)} steps)"
41
+
42
+
43
+ @app.command("list")
44
+ def list_workflows(
45
+ search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
46
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
47
+ ) -> None:
48
+ """List all workflows with details."""
49
+ try:
50
+ with get_client() as client:
51
+ # List API only returns id/name, fetch full details
52
+ workflows_list = client.list_workflows(search=search)
53
+
54
+ data = []
55
+ for wf in workflows_list:
56
+ wf_id = wf.get("id")
57
+ if wf_id:
58
+ response = client.get_workflow(wf_id)
59
+ full_wf = response.get("result", response)
60
+ data.append(full_wf)
61
+
62
+ if output == "json":
63
+ print_json(data)
64
+ else:
65
+ # Format for display
66
+ display_data = []
67
+ for wf in data:
68
+ rules = wf.get("rules", [])
69
+ display_data.append({
70
+ "id": wf.get("id"),
71
+ "name": wf.get("name"),
72
+ "type": _summarize_workflow(wf),
73
+ "rules": len(rules),
74
+ })
75
+
76
+ print_table(
77
+ display_data,
78
+ [("ID", "id"), ("Name", "name"), ("Type", "type"), ("Rules", "rules")],
79
+ title="Workflows",
80
+ )
81
+ except httpx.HTTPStatusError as e:
82
+ print_api_error(e, "list workflows")
83
+ raise typer.Exit(1)
84
+ except httpx.RequestError as e:
85
+ print_api_error(e, "list workflows")
86
+ raise typer.Exit(1)
87
+ except Exception as e:
88
+ print_api_error(e, "list workflows")
89
+ raise typer.Exit(1)
90
+
91
+
92
+ @app.command("get")
93
+ def get_workflow(
94
+ workflow_id: str = typer.Argument(..., help="Workflow ID"),
95
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
96
+ ) -> None:
97
+ """Get a workflow by ID with full details."""
98
+ try:
99
+ with get_client() as client:
100
+ response = client.get_workflow(workflow_id)
101
+
102
+ workflow = response.get("result", response)
103
+
104
+ if output == "json":
105
+ print_json(response)
106
+ else:
107
+ from rich.panel import Panel
108
+ from rich.table import Table
109
+
110
+ name = workflow.get("name", "")
111
+ wf_id = workflow.get("id", "")
112
+ rules = workflow.get("rules", [])
113
+
114
+ console.print(Panel(
115
+ f"[bold]{name}[/bold]\n\n"
116
+ f"[dim]Type:[/dim] {_summarize_workflow(workflow)}\n"
117
+ f"[dim]Rules:[/dim] {len(rules)}",
118
+ title="Workflow Details",
119
+ subtitle=f"ID: {wf_id}",
120
+ ))
121
+
122
+ # Rules table
123
+ if rules:
124
+ for i, rule in enumerate(rules, 1):
125
+ duration = rule.get("underDuration", 0)
126
+ hours = duration // 3600 if duration else 0
127
+
128
+ console.print(f"\n[bold cyan]Rule {i}[/bold cyan]" +
129
+ (f" (≤{hours}h)" if duration else ""))
130
+
131
+ approval_flow = rule.get("approvalFlow", {})
132
+ steps = approval_flow.get("steps", [])
133
+
134
+ if steps:
135
+ table = Table(show_header=True, header_style="bold")
136
+ table.add_column("Step", style="dim", width=6)
137
+ table.add_column("Approvers", style="green")
138
+ table.add_column("Notified", style="yellow")
139
+ table.add_column("Operator", style="cyan")
140
+
141
+ for j, step in enumerate(steps, 1):
142
+ approvers = step.get("approvalEntities", [])
143
+ notified = step.get("notifiedEntities", [])
144
+ operator = step.get("operator", "")
145
+
146
+ # Format approvers
147
+ approver_list = []
148
+ for a in approvers:
149
+ a_type = a.get("type", "")
150
+ entity = a.get("entity")
151
+ if entity:
152
+ approver_list.append(f"{a_type}: {entity.get('name', entity.get('email', ''))}")
153
+ else:
154
+ approver_list.append(a_type)
155
+
156
+ # Format notified
157
+ notified_list = []
158
+ for n in notified:
159
+ n_type = n.get("type", "")
160
+ entity = n.get("entity")
161
+ if entity:
162
+ notified_list.append(f"{n_type}: {entity.get('name', entity.get('email', ''))}")
163
+ else:
164
+ notified_list.append(n_type)
165
+
166
+ table.add_row(
167
+ str(j),
168
+ "\n".join(approver_list) or "-",
169
+ "\n".join(notified_list) or "-",
170
+ operator or "-",
171
+ )
172
+
173
+ console.print(table)
174
+ else:
175
+ console.print("[yellow]No approval steps defined[/yellow]")
176
+ else:
177
+ console.print("\n[yellow]No rules defined[/yellow]")
178
+
179
+ except httpx.HTTPStatusError as e:
180
+ print_api_error(e, "get workflow")
181
+ raise typer.Exit(1)
182
+ except httpx.RequestError as e:
183
+ print_api_error(e, "get workflow")
184
+ raise typer.Exit(1)
185
+ except Exception as e:
186
+ print_api_error(e, "get workflow")
187
+ raise typer.Exit(1)
@@ -0,0 +1,31 @@
1
+ """Pydantic models for Entitle API responses."""
2
+
3
+ from .common import (
4
+ EntityRef,
5
+ UserRef,
6
+ PaginatedResponse,
7
+ )
8
+ from .integration import Integration
9
+ from .resource import Resource
10
+ from .role import Role
11
+ from .bundle import Bundle
12
+ from .workflow import Workflow, WorkflowRule, ApprovalFlow
13
+ from .user import User
14
+ from .permission import Permission
15
+ from .policy import Policy
16
+
17
+ __all__ = [
18
+ "EntityRef",
19
+ "UserRef",
20
+ "PaginatedResponse",
21
+ "Integration",
22
+ "Resource",
23
+ "Role",
24
+ "Bundle",
25
+ "Workflow",
26
+ "WorkflowRule",
27
+ "ApprovalFlow",
28
+ "User",
29
+ "Permission",
30
+ "Policy",
31
+ ]
@@ -0,0 +1,28 @@
1
+ """Bundle model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef
7
+
8
+
9
+ class Bundle(BaseModel):
10
+ """Entitle bundle (cross-application permission package)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ id: str
15
+ name: str
16
+ description: Optional[str] = None
17
+ category: Optional[str] = None
18
+ tags: Optional[list[str]] = None
19
+ workflow: Optional[EntityRef] = None
20
+ roles: Optional[list[EntityRef]] = None
21
+ allowed_durations: Optional[list[int]] = None
22
+ created_at: Optional[str] = None
23
+ updated_at: Optional[str] = None
24
+
25
+ @classmethod
26
+ def from_api(cls, data: dict[str, Any]) -> "Bundle":
27
+ """Create from API response."""
28
+ return cls.model_validate(data)
@@ -0,0 +1,37 @@
1
+ """Common models used across the API."""
2
+
3
+ from typing import Any, Generic, Optional, TypeVar
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+
7
+ class EntityRef(BaseModel):
8
+ """Reference to an entity by ID."""
9
+
10
+ model_config = ConfigDict(extra="allow")
11
+
12
+ id: str
13
+ name: Optional[str] = None
14
+
15
+
16
+ class UserRef(BaseModel):
17
+ """Reference to a user."""
18
+
19
+ model_config = ConfigDict(extra="allow")
20
+
21
+ id: Optional[str] = None
22
+ email: Optional[str] = None
23
+ name: Optional[str] = None
24
+
25
+
26
+ T = TypeVar("T")
27
+
28
+
29
+ class PaginatedResponse(BaseModel, Generic[T]):
30
+ """Paginated API response."""
31
+
32
+ model_config = ConfigDict(extra="allow")
33
+
34
+ data: list[T]
35
+ total: Optional[int] = None
36
+ offset: Optional[int] = None
37
+ limit: Optional[int] = None
@@ -0,0 +1,30 @@
1
+ """Integration model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef, UserRef
7
+
8
+
9
+ class Integration(BaseModel):
10
+ """Entitle integration (connection to external application)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ id: str
15
+ name: str
16
+ application: Optional[str] = None
17
+ owner: Optional[UserRef] = None
18
+ workflow: Optional[EntityRef] = None
19
+ allowed_durations: Optional[list[int]] = None
20
+ allow_creating_accounts: Optional[bool] = None
21
+ allow_changing_account_permissions: Optional[bool] = None
22
+ readonly: Optional[bool] = None
23
+ maintainers: Optional[list[UserRef]] = None
24
+ created_at: Optional[str] = None
25
+ updated_at: Optional[str] = None
26
+
27
+ @classmethod
28
+ def from_api(cls, data: dict[str, Any]) -> "Integration":
29
+ """Create from API response."""
30
+ return cls.model_validate(data)
@@ -0,0 +1,27 @@
1
+ """Permission model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef, UserRef
7
+
8
+
9
+ class Permission(BaseModel):
10
+ """Entitle permission (granted access)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ id: str
15
+ user: Optional[UserRef] = None
16
+ role: Optional[EntityRef] = None
17
+ resource: Optional[EntityRef] = None
18
+ integration: Optional[EntityRef] = None
19
+ status: Optional[str] = None
20
+ granted_at: Optional[str] = None
21
+ expires_at: Optional[str] = None
22
+ created_at: Optional[str] = None
23
+
24
+ @classmethod
25
+ def from_api(cls, data: dict[str, Any]) -> "Permission":
26
+ """Create from API response."""
27
+ return cls.model_validate(data)
@@ -0,0 +1,25 @@
1
+ """Policy model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef
7
+
8
+
9
+ class Policy(BaseModel):
10
+ """Entitle policy (birthright permissions based on group membership)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ id: str
15
+ in_groups: Optional[list[EntityRef]] = None
16
+ roles: Optional[list[EntityRef]] = None
17
+ bundles: Optional[list[EntityRef]] = None
18
+ sort_order: Optional[int] = None
19
+ created_at: Optional[str] = None
20
+ updated_at: Optional[str] = None
21
+
22
+ @classmethod
23
+ def from_api(cls, data: dict[str, Any]) -> "Policy":
24
+ """Create from API response."""
25
+ return cls.model_validate(data)
@@ -0,0 +1,29 @@
1
+ """Resource model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef, UserRef
7
+
8
+
9
+ class Resource(BaseModel):
10
+ """Entitle resource (target system or asset)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ id: str
15
+ name: str
16
+ integration: Optional[EntityRef] = None
17
+ requestable: Optional[bool] = None
18
+ owner: Optional[UserRef] = None
19
+ workflow: Optional[EntityRef] = None
20
+ allowed_durations: Optional[list[int]] = None
21
+ maintainers: Optional[list[UserRef]] = None
22
+ user_defined_tags: Optional[list[str]] = None
23
+ created_at: Optional[str] = None
24
+ updated_at: Optional[str] = None
25
+
26
+ @classmethod
27
+ def from_api(cls, data: dict[str, Any]) -> "Resource":
28
+ """Create from API response."""
29
+ return cls.model_validate(data)
@@ -0,0 +1,28 @@
1
+ """Role model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef
7
+
8
+
9
+ class Role(BaseModel):
10
+ """Entitle role (permission set within a resource)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ id: str
15
+ name: str
16
+ resource: Optional[EntityRef] = None
17
+ requestable: Optional[bool] = None
18
+ allowed_durations: Optional[list[int]] = None
19
+ workflow: Optional[EntityRef] = None
20
+ prerequisite_permissions: Optional[list[EntityRef]] = None
21
+ virtualized_role: Optional[bool] = None
22
+ created_at: Optional[str] = None
23
+ updated_at: Optional[str] = None
24
+
25
+ @classmethod
26
+ def from_api(cls, data: dict[str, Any]) -> "Role":
27
+ """Create from API response."""
28
+ return cls.model_validate(data)
@@ -0,0 +1,24 @@
1
+ """User model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+
7
+ class User(BaseModel):
8
+ """Entitle user."""
9
+
10
+ model_config = ConfigDict(extra="allow")
11
+
12
+ id: str
13
+ email: Optional[str] = None
14
+ name: Optional[str] = None
15
+ first_name: Optional[str] = None
16
+ last_name: Optional[str] = None
17
+ status: Optional[str] = None
18
+ created_at: Optional[str] = None
19
+ updated_at: Optional[str] = None
20
+
21
+ @classmethod
22
+ def from_api(cls, data: dict[str, Any]) -> "User":
23
+ """Create from API response."""
24
+ return cls.model_validate(data)
@@ -0,0 +1,55 @@
1
+ """Workflow model."""
2
+
3
+ from typing import Any, Optional
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from entitle_admin.models.common import EntityRef
7
+
8
+
9
+ class ApprovalEntity(BaseModel):
10
+ """Entity that can approve requests."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ type: str # "Automatic", "User", "Group", etc.
15
+ id: Optional[str] = None
16
+ name: Optional[str] = None
17
+
18
+
19
+ class ApprovalFlow(BaseModel):
20
+ """Single step in approval flow."""
21
+
22
+ model_config = ConfigDict(extra="allow")
23
+
24
+ approval_entities: list[ApprovalEntity] = []
25
+ operator: Optional[str] = None # "and" or "or"
26
+ sort_order: Optional[int] = None
27
+
28
+
29
+ class WorkflowRule(BaseModel):
30
+ """Rule within a workflow."""
31
+
32
+ model_config = ConfigDict(extra="allow")
33
+
34
+ any_schedule: Optional[bool] = None
35
+ schedules: Optional[list[EntityRef]] = None
36
+ for_groups: Optional[list[EntityRef]] = None
37
+ duration: Optional[int] = None # max duration in seconds
38
+ approval_flow: Optional[list[ApprovalFlow]] = None
39
+
40
+
41
+ class Workflow(BaseModel):
42
+ """Entitle workflow (approval process)."""
43
+
44
+ model_config = ConfigDict(extra="allow")
45
+
46
+ id: str
47
+ name: str
48
+ rules: Optional[list[WorkflowRule]] = None
49
+ created_at: Optional[str] = None
50
+ updated_at: Optional[str] = None
51
+
52
+ @classmethod
53
+ def from_api(cls, data: dict[str, Any]) -> "Workflow":
54
+ """Create from API response."""
55
+ return cls.model_validate(data)
@@ -0,0 +1 @@
1
+ """EPM Windows CLI module."""
@@ -0,0 +1,5 @@
1
+ """EPMW client module."""
2
+
3
+ from .base import EPMWClient, get_client
4
+
5
+ __all__ = ["EPMWClient", "get_client"]