day2 0.3.2__tar.gz → 0.3.3__tar.gz
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.
- {day2-0.3.2 → day2-0.3.3}/PKG-INFO +1 -1
- {day2-0.3.2 → day2-0.3.3}/day2/client/base.py +16 -4
- {day2-0.3.2 → day2-0.3.3}/day2/client/config.py +23 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/account.py +20 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/tenant.py +2 -0
- day2-0.3.3/day2/models/user.py +74 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/account.py +20 -5
- day2-0.3.3/day2/resources/user.py +128 -0
- {day2-0.3.2 → day2-0.3.3}/day2.egg-info/PKG-INFO +1 -1
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/account.py +19 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/tenant.py +2 -0
- day2-0.3.3/day2_cli/commands/user.py +162 -0
- {day2-0.3.2 → day2-0.3.3}/pyproject.toml +1 -1
- day2-0.3.2/day2/models/user.py +0 -35
- day2-0.3.2/day2/resources/user.py +0 -70
- day2-0.3.2/day2_cli/commands/user.py +0 -73
- {day2-0.3.2 → day2-0.3.3}/LICENSE +0 -0
- {day2-0.3.2 → day2-0.3.3}/README.md +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/_version.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/auth/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/auth/credentials.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/client/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/client/constants.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/client/profile.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/client/user_context.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/exceptions.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/assessment.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/azure_account.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/azure_assessment.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/bot.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/cost.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/project.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/report.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/resource.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/models/role.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/assessment.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/azure_account.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/azure_assessment.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/bot.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/cost.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/project.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/report.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/resource.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/role.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/resources/tenant.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/session.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/utils/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2/utils/helpers.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2.egg-info/SOURCES.txt +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2.egg-info/dependency_links.txt +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2.egg-info/entry_points.txt +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2.egg-info/requires.txt +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2.egg-info/top_level.txt +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/cli.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/assessment.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/auth.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/azure_account.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/azure_assessment.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/bot.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/cost.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/profile.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/project.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/report.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/resource.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/role.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/commands/tenant_docs_cli.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/utils/__init__.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/utils/context.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/utils/exit_codes.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/utils/formatters.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/utils/helpers.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/day2_cli/utils/output_formatter.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/setup.cfg +0 -0
- {day2-0.3.2 → day2-0.3.3}/tests/test_exceptions.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/tests/test_rate_limiting.py +0 -0
- {day2-0.3.2 → day2-0.3.3}/tests/test_rate_limiting_retry.py +0 -0
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
# Use TYPE_CHECKING to avoid circular imports
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Dict
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
8
8
|
|
|
9
9
|
import requests
|
|
10
10
|
from tenacity import (
|
|
@@ -43,11 +43,14 @@ class BaseClient:
|
|
|
43
43
|
self.service_name = service_name
|
|
44
44
|
self._config = session._config
|
|
45
45
|
|
|
46
|
-
def _get_endpoint_url(
|
|
46
|
+
def _get_endpoint_url(
|
|
47
|
+
self, endpoint: str, api_version: Optional[str] = None
|
|
48
|
+
) -> str:
|
|
47
49
|
"""Get the full URL for an endpoint.
|
|
48
50
|
|
|
49
51
|
Args:
|
|
50
52
|
endpoint: API endpoint path.
|
|
53
|
+
api_version: API version to use instead of the default.(optional)
|
|
51
54
|
|
|
52
55
|
Returns:
|
|
53
56
|
Full URL for the endpoint.
|
|
@@ -55,6 +58,10 @@ class BaseClient:
|
|
|
55
58
|
if endpoint.startswith("/"):
|
|
56
59
|
endpoint = endpoint[1:]
|
|
57
60
|
|
|
61
|
+
# Handle custom version if provided
|
|
62
|
+
if api_version:
|
|
63
|
+
return f"{self._config.get_api_url_with_version(api_version)}/{endpoint}"
|
|
64
|
+
|
|
58
65
|
# For MontyCloud API, we don't include the service name in the URL
|
|
59
66
|
return f"{self._config.api_url}/{endpoint}"
|
|
60
67
|
|
|
@@ -213,13 +220,18 @@ class BaseClient:
|
|
|
213
220
|
return r(_do_request)()
|
|
214
221
|
|
|
215
222
|
def _make_request(
|
|
216
|
-
self,
|
|
223
|
+
self,
|
|
224
|
+
method: str,
|
|
225
|
+
endpoint: str,
|
|
226
|
+
api_version: Optional[str] = None,
|
|
227
|
+
**kwargs: Any,
|
|
217
228
|
) -> Dict[str, Any]:
|
|
218
229
|
"""Make an API request.
|
|
219
230
|
|
|
220
231
|
Args:
|
|
221
232
|
method: HTTP method (GET, POST, PUT, DELETE, etc.).
|
|
222
233
|
endpoint: API endpoint path.
|
|
234
|
+
api_version: API version to use instead of the default (optional).
|
|
223
235
|
**kwargs: Additional arguments to pass to requests.
|
|
224
236
|
|
|
225
237
|
Returns:
|
|
@@ -229,7 +241,7 @@ class BaseClient:
|
|
|
229
241
|
if "json_data" in kwargs:
|
|
230
242
|
kwargs["json"] = kwargs.pop("json_data")
|
|
231
243
|
|
|
232
|
-
url = self._get_endpoint_url(endpoint)
|
|
244
|
+
url = self._get_endpoint_url(endpoint, api_version)
|
|
233
245
|
logger.debug("Making request to %s %s", method, url)
|
|
234
246
|
# Pass the full URL to the request method
|
|
235
247
|
return self._request_with_retry(method, url, **kwargs)
|
|
@@ -5,6 +5,9 @@ from dataclasses import dataclass
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, Dict, Optional
|
|
7
7
|
|
|
8
|
+
API_VERSION_V1 = "v1"
|
|
9
|
+
API_VERSION_V2 = "v2"
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
@dataclass
|
|
10
13
|
class Config:
|
|
@@ -61,6 +64,26 @@ class Config:
|
|
|
61
64
|
"""
|
|
62
65
|
return f"{self.base_url}/{self.api_version}"
|
|
63
66
|
|
|
67
|
+
def get_api_url_with_version(self, api_version: str) -> str:
|
|
68
|
+
"""Get the full API URL with a custom version.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
api_version: API version to use instead of the default.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Full API URL with the specified version.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If the API version is not valid.
|
|
78
|
+
"""
|
|
79
|
+
# Validate API version is in the list of supported versions
|
|
80
|
+
if api_version not in [API_VERSION_V1, API_VERSION_V2]:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Invalid API version '{api_version}'. Valid versions are: v1, v2"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return f"{self.base_url}/{api_version}"
|
|
86
|
+
|
|
64
87
|
def to_dict(self) -> Dict[str, Any]:
|
|
65
88
|
"""Convert config to dictionary.
|
|
66
89
|
|
|
@@ -10,6 +10,8 @@ class GenerateOnboardingTemplateInput(BaseModel):
|
|
|
10
10
|
|
|
11
11
|
Attributes:
|
|
12
12
|
account_type: Type of AWS account to onboard: STANDALONE or MANAGEMENT
|
|
13
|
+
account_name: Name of the AWS account to onboard
|
|
14
|
+
account_number: AWS account number to onboard
|
|
13
15
|
regions: List of AWS regions to onboard
|
|
14
16
|
"""
|
|
15
17
|
|
|
@@ -18,6 +20,16 @@ class GenerateOnboardingTemplateInput(BaseModel):
|
|
|
18
20
|
alias="AccountType",
|
|
19
21
|
description="Type of AWS account to onboard: STANDALONE or MANAGEMENT",
|
|
20
22
|
)
|
|
23
|
+
account_name: str = Field(
|
|
24
|
+
...,
|
|
25
|
+
alias="AccountName",
|
|
26
|
+
description="Name of the AWS account to onboard",
|
|
27
|
+
)
|
|
28
|
+
account_number: str = Field(
|
|
29
|
+
...,
|
|
30
|
+
alias="AccountNumber",
|
|
31
|
+
description="AWS account number to onboard",
|
|
32
|
+
)
|
|
21
33
|
regions: List[str] = Field(
|
|
22
34
|
default_factory=list,
|
|
23
35
|
alias="Regions",
|
|
@@ -29,6 +41,7 @@ class AccountItem(BaseModel):
|
|
|
29
41
|
"""Details of an account.
|
|
30
42
|
|
|
31
43
|
Attributes:
|
|
44
|
+
account_id: Unique account identifier
|
|
32
45
|
number: AWS account number
|
|
33
46
|
name: Account name
|
|
34
47
|
status: Current status of the account
|
|
@@ -37,6 +50,7 @@ class AccountItem(BaseModel):
|
|
|
37
50
|
onboarded_date: Date when the account was onboarded
|
|
38
51
|
"""
|
|
39
52
|
|
|
53
|
+
account_id: str = Field(alias="AccountId")
|
|
40
54
|
number: str = Field(alias="Number")
|
|
41
55
|
name: str = Field(alias="Name")
|
|
42
56
|
status: str = Field(alias="Status")
|
|
@@ -53,6 +67,7 @@ class GenerateOnboardingTemplateOutput(BaseModel):
|
|
|
53
67
|
|
|
54
68
|
Attributes:
|
|
55
69
|
account_id: The account ID for the onboarding template
|
|
70
|
+
expires_on: The expiration date and time of the onboarding template
|
|
56
71
|
onboarding_template_url: URL to the generated onboarding template
|
|
57
72
|
"""
|
|
58
73
|
|
|
@@ -61,6 +76,11 @@ class GenerateOnboardingTemplateOutput(BaseModel):
|
|
|
61
76
|
alias="AccountId",
|
|
62
77
|
description="The account ID for the onboarding template",
|
|
63
78
|
)
|
|
79
|
+
expires_on: str = Field(
|
|
80
|
+
...,
|
|
81
|
+
alias="ExpiresOn",
|
|
82
|
+
description="The expiration date and time of the onboarding template",
|
|
83
|
+
)
|
|
64
84
|
onboarding_template_url: str = Field(
|
|
65
85
|
...,
|
|
66
86
|
alias="OnboardingTemplateURL",
|
|
@@ -125,6 +125,7 @@ class Account(BaseModel):
|
|
|
125
125
|
"""Details of an account.
|
|
126
126
|
|
|
127
127
|
Attributes:
|
|
128
|
+
account_id: Unique account identifier
|
|
128
129
|
number: AWS account number
|
|
129
130
|
name: Account name
|
|
130
131
|
status: Current status of the account
|
|
@@ -133,6 +134,7 @@ class Account(BaseModel):
|
|
|
133
134
|
onboarded_date: Date when the account was onboarded
|
|
134
135
|
"""
|
|
135
136
|
|
|
137
|
+
account_id: str = Field(alias="AccountId")
|
|
136
138
|
number: str = Field(alias="Number")
|
|
137
139
|
name: str = Field(alias="Name")
|
|
138
140
|
status: str = Field(alias="Status")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""User models for the MontyCloud SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class User(BaseModel):
|
|
9
|
+
"""Details of a user in a tenant.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
name: Name of the user
|
|
13
|
+
email: Email address of the user
|
|
14
|
+
user_id: Unique identifier of the user
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
name: str = Field(alias="Name")
|
|
18
|
+
email: str = Field(alias="Email")
|
|
19
|
+
user_id: str = Field(alias="UserId")
|
|
20
|
+
|
|
21
|
+
# Allow extra fields
|
|
22
|
+
model_config = ConfigDict(extra="allow")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ListUsersOutput(BaseModel):
|
|
26
|
+
"""Output model for ListUsers API.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
users: List of users in the tenant
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
users: List[User] = Field(alias="Users", default=[])
|
|
33
|
+
|
|
34
|
+
# Allow extra fields
|
|
35
|
+
model_config = ConfigDict(extra="allow")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CreateUserInput(BaseModel):
|
|
39
|
+
"""
|
|
40
|
+
Input model for CreateUser API.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
name: Name of the user
|
|
44
|
+
email: Email address of the user
|
|
45
|
+
temporary_password: Temporary password for the user
|
|
46
|
+
role_id: Role ID for the user
|
|
47
|
+
projects: (Optional) Project(s) List of project ids for project roles. Applicable for project specific roles
|
|
48
|
+
federated_access_roles: (Optional) List of federated access roles. Applicable for admin roles
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
name: str = Field(alias="Name")
|
|
52
|
+
email: str = Field(alias="Email")
|
|
53
|
+
temporary_password: str = Field(alias="TemporaryPassword")
|
|
54
|
+
role_id: str = Field(alias="RoleId")
|
|
55
|
+
projects: Optional[List[str]] = Field(None, alias="Projects")
|
|
56
|
+
federated_access_roles: Optional[List[str]] = Field(
|
|
57
|
+
None, alias="FederatedAccessRoles"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Allow extra fields
|
|
61
|
+
model_config = ConfigDict(extra="allow")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CreateUserOutput(BaseModel):
|
|
65
|
+
"""Output model for CreateUser API.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
user_id: Unique identifier of the created user
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
user_id: str = Field(alias="UserId")
|
|
72
|
+
|
|
73
|
+
# Allow extra fields
|
|
74
|
+
model_config = ConfigDict(extra="allow")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
from day2.client.base import BaseClient
|
|
6
|
+
from day2.client.config import API_VERSION_V2
|
|
6
7
|
from day2.models.account import (
|
|
7
8
|
GenerateOnboardingTemplateInput,
|
|
8
9
|
GenerateOnboardingTemplateOutput,
|
|
@@ -24,17 +25,24 @@ class AccountClient(BaseClient):
|
|
|
24
25
|
super().__init__(session, "account")
|
|
25
26
|
|
|
26
27
|
def get_onboarding_template(
|
|
27
|
-
self,
|
|
28
|
+
self,
|
|
29
|
+
tenant_id: str,
|
|
30
|
+
account_type: str,
|
|
31
|
+
account_name: str,
|
|
32
|
+
account_number: str,
|
|
33
|
+
regions: Optional[List[str]] = None,
|
|
28
34
|
) -> GenerateOnboardingTemplateOutput:
|
|
29
35
|
"""Generate an AWS onboarding template for the specified tenant.
|
|
30
36
|
|
|
31
37
|
Args:
|
|
32
38
|
tenant_id: The ID of the tenant to generate the onboarding template for.
|
|
33
39
|
account_type: Type of AWS account to onboard: STANDALONE or MANAGEMENT.
|
|
40
|
+
account_name: Name of the AWS account to onboard.
|
|
41
|
+
account_number: AWS account number to onboard.
|
|
34
42
|
regions: List of AWS regions to onboard - Required for Assessment Feature type (Audit Permissions), not supported by other feature types (AutomatedCloudOps, ContinuousVisibility).
|
|
35
43
|
|
|
36
44
|
Returns:
|
|
37
|
-
GenerateOnboardingTemplateOutput: Object containing the account ID and onboarding template URL.
|
|
45
|
+
GenerateOnboardingTemplateOutput: Object containing the account ID, expiration date and time of the onboarding link and onboarding template URL.
|
|
38
46
|
|
|
39
47
|
Raises:
|
|
40
48
|
ClientError: If the request is invalid.
|
|
@@ -47,6 +55,8 @@ class AccountClient(BaseClient):
|
|
|
47
55
|
>>> response = client.get_onboarding_template(
|
|
48
56
|
... tenant_id="tenant-123",
|
|
49
57
|
... account_type="STANDALONE", # STANDALONE or MANAGEMENT
|
|
58
|
+
... account_name="AcmeAccount",
|
|
59
|
+
... account_number="123456789012",
|
|
50
60
|
... regions=[
|
|
51
61
|
... "us-east-1",
|
|
52
62
|
... "us-west-2"
|
|
@@ -56,12 +66,16 @@ class AccountClient(BaseClient):
|
|
|
56
66
|
>>> print(f"Onboarding URL: {response.onboarding_template_url}")
|
|
57
67
|
"""
|
|
58
68
|
data = GenerateOnboardingTemplateInput(
|
|
59
|
-
AccountType=account_type,
|
|
69
|
+
AccountType=account_type,
|
|
70
|
+
AccountName=account_name,
|
|
71
|
+
AccountNumber=account_number,
|
|
72
|
+
Regions=regions if regions is not None else [],
|
|
60
73
|
)
|
|
61
74
|
|
|
62
75
|
response = self._make_request(
|
|
63
76
|
"POST",
|
|
64
77
|
f"tenants/{tenant_id}/accounts/aws/onboarding-template",
|
|
78
|
+
API_VERSION_V2,
|
|
65
79
|
json=data.model_dump(by_alias=True),
|
|
66
80
|
)
|
|
67
81
|
return GenerateOnboardingTemplateOutput.model_validate(response)
|
|
@@ -89,7 +103,8 @@ class AccountClient(BaseClient):
|
|
|
89
103
|
>>> client = session.account
|
|
90
104
|
>>> response = client.list_accounts(tenant_id="your-tenant-id", page_size=10)
|
|
91
105
|
>>> for account in response.accounts:
|
|
92
|
-
... print(f"
|
|
106
|
+
... print(f"Account ID: {account.account_id}")
|
|
107
|
+
... print(f"Account Name: {account.name}")
|
|
93
108
|
"""
|
|
94
109
|
params: dict[str, object] = {
|
|
95
110
|
"PageSize": page_size,
|
|
@@ -97,7 +112,7 @@ class AccountClient(BaseClient):
|
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
response = self._make_request(
|
|
100
|
-
"GET", f"tenants/{tenant_id}/accounts", params=params
|
|
115
|
+
"GET", f"tenants/{tenant_id}/accounts/", params=params
|
|
101
116
|
)
|
|
102
117
|
return ListAccountsOutput.model_validate(response)
|
|
103
118
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""User resource client for the MontyCloud DAY2 SDK."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from day2.client.base import BaseClient
|
|
7
|
+
from day2.client.user_context import UserContext
|
|
8
|
+
from day2.models.user import CreateUserInput, CreateUserOutput, ListUsersOutput
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from day2.session import Session
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UserClient(BaseClient):
|
|
17
|
+
"""Client for interacting with Users."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: "Session") -> None:
|
|
20
|
+
"""Initialize a new UserClient.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
session: MontyCloud session.
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(session, "user")
|
|
26
|
+
|
|
27
|
+
def get_user(self) -> Optional[UserContext]:
|
|
28
|
+
"""Get information about the authenticated user.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
UserContext containing the user's information if successful,
|
|
32
|
+
None if the request fails.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
# Use _make_request instead of directly calling session.request
|
|
36
|
+
response_data = self._make_request("GET", "auth/user")
|
|
37
|
+
if response_data:
|
|
38
|
+
logger.debug("Retrieved user context: %s", response_data)
|
|
39
|
+
return UserContext.from_dict(response_data)
|
|
40
|
+
logger.warning("Failed to get user context: empty response")
|
|
41
|
+
return None
|
|
42
|
+
except Exception as e: # pylint: disable=broad-except
|
|
43
|
+
logger.warning("Error retrieving user context: %s", e)
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def list_users(self, tenant_id: str) -> ListUsersOutput:
|
|
47
|
+
"""Retrieves list of Users for a specific tenant.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
tenant_id: ID of the tenant to list users for.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
ListUsersOutput: Object containing list of users.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ResourceNotFoundError: If the tenant does not exist.
|
|
57
|
+
ClientError: If the request is invalid.
|
|
58
|
+
ServerError: If an internal server error occurs.
|
|
59
|
+
AuthenticationError: If authentication fails.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
63
|
+
>>> client = session.user
|
|
64
|
+
>>> response = client.list_users(tenant_id="your-tenant-id")
|
|
65
|
+
>>> for user in response.users:
|
|
66
|
+
... print(f"{user.user_id}: {user.name} {user.email}")
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
response = self._make_request("GET", f"tenants/{tenant_id}/users")
|
|
70
|
+
return ListUsersOutput.model_validate(response)
|
|
71
|
+
|
|
72
|
+
def create_user(self, tenant_id: str, data: CreateUserInput) -> CreateUserOutput:
|
|
73
|
+
"""Create user for a tenant.
|
|
74
|
+
|
|
75
|
+
!!! info "Federated Access Roles"
|
|
76
|
+
This operation is available only if the tenant's **FAA** feature is enabled.
|
|
77
|
+
|
|
78
|
+
- Allowed FAA roles: `AdminUser`, `PowerUser`, `ReadOnlyUser`
|
|
79
|
+
- Users with the **Business Admin** or **Cloud Admin** role may be assigned **any** FAA role.
|
|
80
|
+
- Users with the **Read Only** role can be assigned **only** the `ReadOnlyUser` FAA role.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
tenant_id: ID of the tenant to create user for.
|
|
84
|
+
data: The user data to create.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
CreateUserOutput: Object containing the created user id.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ClientError: If the request is invalid.
|
|
91
|
+
ServerError: If an internal server error occurs.
|
|
92
|
+
AuthenticationError: If authentication fails.
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
96
|
+
>>> client = session.user
|
|
97
|
+
# The payload for creating a user varies based on the role type:
|
|
98
|
+
# 1. For Cloud Admin and Business Admin roles, FederatedAccessRoles can include one or more of PowerUser, ReadOnlyUser, AdminUser, or omitted.
|
|
99
|
+
# 2. For the Read Only role, FederatedAccessRoles must be ReadOnlyUser or omitted.
|
|
100
|
+
# 3. For Project Owner, Project User, and Reports Access roles, the Projects field must include at least one project ID and FederatedAccessRoles should not be provided.
|
|
101
|
+
>>> data = CreateUserInput(
|
|
102
|
+
... Name="UserName",
|
|
103
|
+
... Email="username@example.com",
|
|
104
|
+
... TemporaryPassword="*********",
|
|
105
|
+
... RoleId="13acd9eb-6a65-41f8-a941-d4c6edb3f1c9",
|
|
106
|
+
... Projects = ["8e5a5792-89a4-45fc-9260-dd673015df03"],
|
|
107
|
+
... )
|
|
108
|
+
>>> response = client.create_user(tenant_id="your-tenant-id", data=data)
|
|
109
|
+
>>> print(f"Created user with ID: {response.user_id}")
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
create_user_payload: dict[str, str | list[str]] = {
|
|
113
|
+
"Name": data.name,
|
|
114
|
+
"Email": data.email,
|
|
115
|
+
"TemporaryPassword": data.temporary_password,
|
|
116
|
+
"RoleId": data.role_id,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Add optional fields if they have values
|
|
120
|
+
if data.projects:
|
|
121
|
+
create_user_payload["Projects"] = data.projects
|
|
122
|
+
if data.federated_access_roles:
|
|
123
|
+
create_user_payload["FederatedAccessRoles"] = data.federated_access_roles
|
|
124
|
+
|
|
125
|
+
response = self._make_request(
|
|
126
|
+
"POST", f"tenants/{tenant_id}/users", json=create_user_payload
|
|
127
|
+
)
|
|
128
|
+
return CreateUserOutput.model_validate(response)
|
|
@@ -26,6 +26,18 @@ def account() -> None:
|
|
|
26
26
|
required=True,
|
|
27
27
|
help="Type of AWS account to onboard: STANDALONE or MANAGEMENT",
|
|
28
28
|
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--account-name",
|
|
31
|
+
type=str,
|
|
32
|
+
required=True,
|
|
33
|
+
help="Name of the AWS account to onboard.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--account-number",
|
|
37
|
+
type=str,
|
|
38
|
+
required=True,
|
|
39
|
+
help="AWS account number to onboard.",
|
|
40
|
+
)
|
|
29
41
|
@click.option(
|
|
30
42
|
"--regions",
|
|
31
43
|
type=str,
|
|
@@ -35,6 +47,8 @@ def account() -> None:
|
|
|
35
47
|
@with_common_options(include_tenant_id=True)
|
|
36
48
|
def generate_onboarding_template(
|
|
37
49
|
account_type: str,
|
|
50
|
+
account_name: str,
|
|
51
|
+
account_number: str,
|
|
38
52
|
regions: str,
|
|
39
53
|
output: Optional[str] = None,
|
|
40
54
|
profile: Optional[str] = None,
|
|
@@ -63,12 +77,15 @@ def generate_onboarding_template(
|
|
|
63
77
|
result = session.account.get_onboarding_template(
|
|
64
78
|
tenant_id=resolved_tenant_id,
|
|
65
79
|
account_type=account_type.upper(),
|
|
80
|
+
account_name=account_name,
|
|
81
|
+
account_number=account_number,
|
|
66
82
|
regions=regions_list,
|
|
67
83
|
)
|
|
68
84
|
|
|
69
85
|
# Convert template details to dictionary for output
|
|
70
86
|
template_dict = {
|
|
71
87
|
"account_id": result.account_id,
|
|
88
|
+
"expires_on": result.expires_on,
|
|
72
89
|
"onboarding_template_url": result.onboarding_template_url,
|
|
73
90
|
}
|
|
74
91
|
|
|
@@ -130,6 +147,7 @@ def list_accounts(
|
|
|
130
147
|
account_list = []
|
|
131
148
|
for account_item in result.accounts:
|
|
132
149
|
account_dict = {
|
|
150
|
+
"account_id": account_item.account_id,
|
|
133
151
|
"number": account_item.number,
|
|
134
152
|
"name": account_item.name,
|
|
135
153
|
"status": account_item.status,
|
|
@@ -141,6 +159,7 @@ def list_accounts(
|
|
|
141
159
|
|
|
142
160
|
# Define columns for table output
|
|
143
161
|
columns = {
|
|
162
|
+
"account_id": "Account ID",
|
|
144
163
|
"number": "Number",
|
|
145
164
|
"name": "Name",
|
|
146
165
|
"status": "Status",
|
|
@@ -199,6 +199,7 @@ def list_accounts(
|
|
|
199
199
|
account_list = []
|
|
200
200
|
for account_item in result.accounts:
|
|
201
201
|
account_dict = {
|
|
202
|
+
"account_id": account_item.account_id,
|
|
202
203
|
"number": account_item.number,
|
|
203
204
|
"name": account_item.name,
|
|
204
205
|
"status": account_item.status,
|
|
@@ -210,6 +211,7 @@ def list_accounts(
|
|
|
210
211
|
|
|
211
212
|
# Define columns for table output
|
|
212
213
|
columns = {
|
|
214
|
+
"account_id": "Account ID",
|
|
213
215
|
"number": "Number",
|
|
214
216
|
"name": "Name",
|
|
215
217
|
"status": "Status",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""User commands for the MontyCloud DAY2 CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from day2.exceptions import Day2Error, ProfileNotFoundError
|
|
9
|
+
from day2.models.user import CreateUserInput
|
|
10
|
+
from day2_cli.utils.context import get_enhanced_context, with_common_options
|
|
11
|
+
from day2_cli.utils.exit_codes import handle_error_with_exit
|
|
12
|
+
from day2_cli.utils.output_formatter import format_item_output, format_list_output
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
def user() -> None:
|
|
19
|
+
"""User commands."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@user.command("list")
|
|
23
|
+
@with_common_options(include_tenant_id=True)
|
|
24
|
+
def list_users(
|
|
25
|
+
output: Optional[str] = None,
|
|
26
|
+
profile: Optional[str] = None,
|
|
27
|
+
tenant_id: Optional[str] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""List users associated with a specific tenant.
|
|
30
|
+
|
|
31
|
+
This command lists all the users in the tenant.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# Get enhanced context
|
|
35
|
+
context = get_enhanced_context(
|
|
36
|
+
output=output, profile=profile, tenant_id=tenant_id, require_tenant=True
|
|
37
|
+
)
|
|
38
|
+
session = context["session"]
|
|
39
|
+
resolved_tenant_id = context["tenant_id"]
|
|
40
|
+
output_format = context["output_format"]
|
|
41
|
+
|
|
42
|
+
result = session.user.list_users(resolved_tenant_id)
|
|
43
|
+
|
|
44
|
+
if not result.users:
|
|
45
|
+
console.print("[yellow]No users found.[/yellow]")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Convert user objects to dictionaries for output
|
|
49
|
+
user_list = []
|
|
50
|
+
for user_item in result.users:
|
|
51
|
+
user_dict = {
|
|
52
|
+
"name": user_item.name,
|
|
53
|
+
"email": user_item.email,
|
|
54
|
+
"user_id": user_item.user_id,
|
|
55
|
+
}
|
|
56
|
+
user_list.append(user_dict)
|
|
57
|
+
|
|
58
|
+
# Define columns for table output
|
|
59
|
+
columns = {
|
|
60
|
+
"name": "Name",
|
|
61
|
+
"email": "Email",
|
|
62
|
+
"user_id": "User ID",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Format and output the user list
|
|
66
|
+
format_list_output(
|
|
67
|
+
user_list,
|
|
68
|
+
f"Users for Tenant: {resolved_tenant_id}",
|
|
69
|
+
columns,
|
|
70
|
+
output_format,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
except (Day2Error, ProfileNotFoundError) as e:
|
|
74
|
+
handle_error_with_exit(e)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@user.command("create")
|
|
78
|
+
@click.option("--name", required=True, help="Name of the user")
|
|
79
|
+
@click.option("--email", required=True, help="Email id of the user")
|
|
80
|
+
@click.option(
|
|
81
|
+
"--temporary-password", required=True, help="Temporary password of the user"
|
|
82
|
+
)
|
|
83
|
+
@click.option("--role-id", required=True, help="Role ID to be assigned to the user")
|
|
84
|
+
@click.option(
|
|
85
|
+
"--projects",
|
|
86
|
+
help="Comma-separated list of project ids to be assigned to the user",
|
|
87
|
+
)
|
|
88
|
+
@click.option(
|
|
89
|
+
"--federated-access-roles",
|
|
90
|
+
help="Comma-separated list of federated access roles to be assigned to the user",
|
|
91
|
+
)
|
|
92
|
+
@with_common_options(include_tenant_id=True)
|
|
93
|
+
def create_user(
|
|
94
|
+
name: str,
|
|
95
|
+
email: str,
|
|
96
|
+
temporary_password: str,
|
|
97
|
+
role_id: str,
|
|
98
|
+
projects: Optional[str],
|
|
99
|
+
federated_access_roles: Optional[str],
|
|
100
|
+
output: Optional[str] = None,
|
|
101
|
+
profile: Optional[str] = None,
|
|
102
|
+
tenant_id: Optional[str] = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Create user for a tenant.
|
|
105
|
+
|
|
106
|
+
This command creates a new user for a tenant.
|
|
107
|
+
|
|
108
|
+
!!! info "Federated Access Roles"
|
|
109
|
+
This operation is available only if the tenant's **FAA** feature is enabled.
|
|
110
|
+
|
|
111
|
+
- Allowed FAA roles: `AdminUser`, `PowerUser`, `ReadOnlyUser`
|
|
112
|
+
- Users with the **Business Admin** or **Cloud Admin** role may be assigned **any** FAA role.
|
|
113
|
+
- Users with the **Read Only** role can be assigned **only** the `ReadOnlyUser` FAA role.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# Get enhanced context
|
|
117
|
+
context = get_enhanced_context(
|
|
118
|
+
output=output, profile=profile, tenant_id=tenant_id, require_tenant=True
|
|
119
|
+
)
|
|
120
|
+
session = context["session"]
|
|
121
|
+
resolved_tenant_id = context["tenant_id"]
|
|
122
|
+
output_format = context["output_format"]
|
|
123
|
+
|
|
124
|
+
# Parse projects if provided
|
|
125
|
+
project_ids = (
|
|
126
|
+
[project.strip() for project in projects.split(",")] if projects else None
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
federated_access_roles_list = (
|
|
130
|
+
[role.strip() for role in federated_access_roles.split(",")]
|
|
131
|
+
if federated_access_roles
|
|
132
|
+
else None
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Create a CreateUserInput object
|
|
136
|
+
create_user_input = CreateUserInput(
|
|
137
|
+
Name=name,
|
|
138
|
+
Email=email,
|
|
139
|
+
TemporaryPassword=temporary_password,
|
|
140
|
+
RoleId=role_id,
|
|
141
|
+
Projects=project_ids,
|
|
142
|
+
FederatedAccessRoles=federated_access_roles_list,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
result = session.user.create_user(
|
|
146
|
+
tenant_id=resolved_tenant_id, data=create_user_input
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Create user data for output
|
|
150
|
+
user_output_data = {
|
|
151
|
+
"userId": result.user_id,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Format and output the result
|
|
155
|
+
format_item_output(
|
|
156
|
+
item=user_output_data,
|
|
157
|
+
title="User Created Successfully",
|
|
158
|
+
format_override=output_format,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except (Day2Error, ProfileNotFoundError) as e:
|
|
162
|
+
handle_error_with_exit(e)
|
day2-0.3.2/day2/models/user.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""User models for the MontyCloud SDK."""
|
|
2
|
-
|
|
3
|
-
from typing import List
|
|
4
|
-
|
|
5
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class User(BaseModel):
|
|
9
|
-
"""Details of a user in a tenant.
|
|
10
|
-
|
|
11
|
-
Attributes:
|
|
12
|
-
name: Name of the user
|
|
13
|
-
email: Email address of the user
|
|
14
|
-
user_id: Unique identifier of the user
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
name: str = Field(alias="Name")
|
|
18
|
-
email: str = Field(alias="Email")
|
|
19
|
-
user_id: str = Field(alias="UserId")
|
|
20
|
-
|
|
21
|
-
# Allow extra fields
|
|
22
|
-
model_config = ConfigDict(extra="allow")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ListUsersOutput(BaseModel):
|
|
26
|
-
"""Output model for ListUsers API.
|
|
27
|
-
|
|
28
|
-
Attributes:
|
|
29
|
-
users: List of users in the tenant
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
users: List[User] = Field(alias="Users", default=[])
|
|
33
|
-
|
|
34
|
-
# Allow extra fields
|
|
35
|
-
model_config = ConfigDict(extra="allow")
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"""User resource client for the MontyCloud DAY2 SDK."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from typing import TYPE_CHECKING, Optional
|
|
5
|
-
|
|
6
|
-
from day2.client.base import BaseClient
|
|
7
|
-
from day2.client.user_context import UserContext
|
|
8
|
-
from day2.models.user import ListUsersOutput
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from day2.session import Session
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class UserClient(BaseClient):
|
|
17
|
-
"""Client for interacting with Users."""
|
|
18
|
-
|
|
19
|
-
def __init__(self, session: "Session") -> None:
|
|
20
|
-
"""Initialize a new UserClient.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
session: MontyCloud session.
|
|
24
|
-
"""
|
|
25
|
-
super().__init__(session, "user")
|
|
26
|
-
|
|
27
|
-
def get_user(self) -> Optional[UserContext]:
|
|
28
|
-
"""Get information about the authenticated user.
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
UserContext containing the user's information if successful,
|
|
32
|
-
None if the request fails.
|
|
33
|
-
"""
|
|
34
|
-
try:
|
|
35
|
-
# Use _make_request instead of directly calling session.request
|
|
36
|
-
response_data = self._make_request("GET", "auth/user")
|
|
37
|
-
if response_data:
|
|
38
|
-
logger.debug("Retrieved user context: %s", response_data)
|
|
39
|
-
return UserContext.from_dict(response_data)
|
|
40
|
-
logger.warning("Failed to get user context: empty response")
|
|
41
|
-
return None
|
|
42
|
-
except Exception as e: # pylint: disable=broad-except
|
|
43
|
-
logger.warning("Error retrieving user context: %s", e)
|
|
44
|
-
return None
|
|
45
|
-
|
|
46
|
-
def list_users(self, tenant_id: str) -> ListUsersOutput:
|
|
47
|
-
"""Retrieves list of Users for a specific tenant.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
tenant_id: ID of the tenant to list users for.
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
ListUsersOutput: Object containing list of users.
|
|
54
|
-
|
|
55
|
-
Raises:
|
|
56
|
-
ResourceNotFoundError: If the tenant does not exist.
|
|
57
|
-
ClientError: If the request is invalid.
|
|
58
|
-
ServerError: If an internal server error occurs.
|
|
59
|
-
AuthenticationError: If authentication fails.
|
|
60
|
-
|
|
61
|
-
Examples:
|
|
62
|
-
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
63
|
-
>>> client = session.user
|
|
64
|
-
>>> response = client.list_users(tenant_id="your-tenant-id")
|
|
65
|
-
>>> for user in response.users:
|
|
66
|
-
... print(f"{user.user_id}: {user.name} {user.email}")
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
response = self._make_request("GET", f"tenants/{tenant_id}/users")
|
|
70
|
-
return ListUsersOutput.model_validate(response)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""User commands for the MontyCloud DAY2 CLI."""
|
|
2
|
-
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
|
|
8
|
-
from day2.exceptions import Day2Error, ProfileNotFoundError
|
|
9
|
-
from day2_cli.utils.context import get_enhanced_context, with_common_options
|
|
10
|
-
from day2_cli.utils.exit_codes import handle_error_with_exit
|
|
11
|
-
from day2_cli.utils.output_formatter import format_list_output
|
|
12
|
-
|
|
13
|
-
console = Console()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@click.group()
|
|
17
|
-
def user() -> None:
|
|
18
|
-
"""User commands."""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@user.command("list")
|
|
22
|
-
@with_common_options(include_tenant_id=True)
|
|
23
|
-
def list_users(
|
|
24
|
-
output: Optional[str] = None,
|
|
25
|
-
profile: Optional[str] = None,
|
|
26
|
-
tenant_id: Optional[str] = None,
|
|
27
|
-
) -> None:
|
|
28
|
-
"""List users associated with a specific tenant.
|
|
29
|
-
|
|
30
|
-
This command lists all the users in the tenant.
|
|
31
|
-
"""
|
|
32
|
-
try:
|
|
33
|
-
# Get enhanced context
|
|
34
|
-
context = get_enhanced_context(
|
|
35
|
-
output=output, profile=profile, tenant_id=tenant_id, require_tenant=True
|
|
36
|
-
)
|
|
37
|
-
session = context["session"]
|
|
38
|
-
resolved_tenant_id = context["tenant_id"]
|
|
39
|
-
output_format = context["output_format"]
|
|
40
|
-
|
|
41
|
-
result = session.user.list_users(resolved_tenant_id)
|
|
42
|
-
|
|
43
|
-
if not result.users:
|
|
44
|
-
console.print("[yellow]No users found.[/yellow]")
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
# Convert user objects to dictionaries for output
|
|
48
|
-
user_list = []
|
|
49
|
-
for user_item in result.users:
|
|
50
|
-
user_dict = {
|
|
51
|
-
"name": user_item.name,
|
|
52
|
-
"email": user_item.email,
|
|
53
|
-
"user_id": user_item.user_id,
|
|
54
|
-
}
|
|
55
|
-
user_list.append(user_dict)
|
|
56
|
-
|
|
57
|
-
# Define columns for table output
|
|
58
|
-
columns = {
|
|
59
|
-
"name": "Name",
|
|
60
|
-
"email": "Email",
|
|
61
|
-
"user_id": "User ID",
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
# Format and output the user list
|
|
65
|
-
format_list_output(
|
|
66
|
-
user_list,
|
|
67
|
-
f"Users for Tenant: {resolved_tenant_id}",
|
|
68
|
-
columns,
|
|
69
|
-
output_format,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
except (Day2Error, ProfileNotFoundError) as e:
|
|
73
|
-
handle_error_with_exit(e)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|