reverse-diagrams 1.3.3__py3-none-any.whl → 1.3.5__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.
src/aws/describe_sso.py CHANGED
@@ -1,175 +1,203 @@
1
1
  """Describe SSO."""
2
2
  import logging
3
+ from typing import List, Dict, Any, Optional
3
4
 
4
- from boto3 import client
5
+ from .client_manager import get_client_manager
6
+ from .exceptions import AWSServiceError
7
+ from ..utils.progress import track_operation
8
+ from ..config import get_config
5
9
 
10
+ logger = logging.getLogger(__name__)
6
11
 
7
- def list_instances(region: str):
8
- """
9
- List all instances in the region.
10
12
 
11
- :param region:
12
- :return:
13
+ def list_instances(region: str) -> List[Dict[str, Any]]:
13
14
  """
14
- sso_client = client("sso-admin", region_name=region)
15
- response = sso_client.list_instances()
16
-
17
- return response["Instances"]
15
+ List all SSO instances in the region.
18
16
 
17
+ Args:
18
+ region: AWS region name
19
19
 
20
- # def list account assignments with pagination
21
- def list_account_assignments_pag(
22
- instance_arn, account_id, permission_set_arn, region, next_token
23
- ):
24
- """
25
- List all account assignments.
26
-
27
- :param instance_arn:
28
- :param account_id:
29
- :param permission_set_arn:
30
- :param region:
31
- :return:
20
+ Returns:
21
+ List of SSO instances
32
22
 
23
+ Raises:
24
+ AWSServiceError: If the API call fails
33
25
  """
34
- sso_client = client("sso-admin", region_name=region)
35
- paginator = sso_client.get_paginator("list_account_assignments")
36
- response_iterator = paginator.paginate(
37
- InstanceArn=instance_arn,
38
- AccountId=account_id,
39
- PermissionSetArn=permission_set_arn,
40
- PaginationConfig={
41
- "MaxItems": 1000,
42
- "PageSize": 20,
43
- "StartingToken": next_token,
44
- },
45
- )
46
- return response_iterator["AccountAssignments"]
26
+ client_manager = get_client_manager(region=region)
27
+
28
+ with track_operation("Listing SSO instances") as task_id:
29
+ response = client_manager.call_api("sso-admin", "list_instances")
30
+ instances = response.get("Instances", [])
31
+ logger.info(f"Found {len(instances)} SSO instances")
32
+ return instances
47
33
 
48
34
 
49
35
  def list_account_assignments(
50
- instance_arn, account_id, permission_set_arn, region, sso_client
51
- ):
36
+ instance_arn: str,
37
+ account_id: str,
38
+ permission_set_arn: str,
39
+ region: str
40
+ ) -> List[Dict[str, Any]]:
52
41
  """
53
- List all account assignments.
42
+ List all account assignments for a permission set.
54
43
 
55
- :param sso_client:
56
- :param instance_arn:
57
- :param account_id:
58
- :param permission_set_arn:
59
- :param region:
60
- :return:
44
+ Args:
45
+ instance_arn: SSO instance ARN
46
+ account_id: AWS account ID
47
+ permission_set_arn: Permission set ARN
48
+ region: AWS region name
61
49
 
50
+ Returns:
51
+ List of account assignments
62
52
  """
63
- response = sso_client.list_account_assignments(
64
- InstanceArn=instance_arn,
65
- AccountId=account_id,
66
- PermissionSetArn=permission_set_arn,
67
- MaxResults=50,
68
- )
69
- account_assignments = response["AccountAssignments"]
70
- if len(response["AccountAssignments"]) >= 50:
71
- logging.info("Paginating ...")
72
- response_iterator = list_account_assignments_pag(
73
- instance_arn,
74
- account_id,
75
- permission_set_arn,
76
- region,
77
- next_token=response["NextToken"],
53
+ client_manager = get_client_manager(region=region)
54
+ config = get_config()
55
+
56
+ try:
57
+ # Use paginated API call for better handling of large result sets
58
+ assignments = client_manager.paginate_api_call(
59
+ "sso-admin",
60
+ "list_account_assignments",
61
+ "AccountAssignments",
62
+ InstanceArn=instance_arn,
63
+ AccountId=account_id,
64
+ PermissionSetArn=permission_set_arn,
65
+ PaginationConfig={
66
+ 'MaxItems': config.pagination.max_items,
67
+ 'PageSize': config.pagination.default_page_size
68
+ }
78
69
  )
79
- for response in response_iterator:
80
- logging.debug(response)
81
- account_assignments.append(response["AccountAssignments"])
82
- logging.info(response["AccountAssignments"])
83
- return account_assignments
70
+
71
+ logger.debug(f"Retrieved {len(assignments)} account assignments for account {account_id}")
72
+ return assignments
73
+
74
+ except Exception as e:
75
+ logger.error(f"Failed to list account assignments for account {account_id}: {e}")
76
+ raise AWSServiceError(f"Failed to list account assignments: {e}")
84
77
 
85
78
 
86
- def list_permissions_set_pag(instance_arn, region, next_token):
79
+ def list_permissions_set(instance_arn: str, region: str) -> List[str]:
87
80
  """
88
- List all permission set in a region.
81
+ List all permission sets in an SSO instance.
89
82
 
90
- :param next_token:
91
- :param instance_arn:
92
- :param region:
93
- :return:
94
- """
95
- sso_client = client("sso-admin", region_name=region)
96
- paginator = sso_client.get_paginator("list_permission_sets")
97
- response_iterator = paginator.paginate(
98
- InstanceArn=instance_arn,
99
- PaginationConfig={
100
- "MaxItems": 1000,
101
- "PageSize": 20,
102
- "StartingToken": next_token,
103
- },
104
- )
105
- response_iterator = response_iterator.build_full_result()
106
- return response_iterator["PermissionSets"]
107
-
108
-
109
- def list_permissions_set(instance_arn, region):
110
- """
111
- List all permission set in a region.
83
+ Args:
84
+ instance_arn: SSO instance ARN
85
+ region: AWS region name
112
86
 
113
- :param instance_arn:
114
- :param region:
115
- :return:
87
+ Returns:
88
+ List of permission set ARNs
116
89
  """
117
- sso_client = client(
118
- "sso-admin",
119
- region_name=region,
120
- )
121
- response = sso_client.list_permission_sets(InstanceArn=instance_arn, MaxResults=20)
122
- logging.debug(response)
123
- permissions_set = response["PermissionSets"]
124
-
125
- if len(response["PermissionSets"]) >= 20:
126
- logging.info("Paginating ...")
127
- response_iterator = list_permissions_set_pag(
128
- instance_arn, region, next_token=response["NextToken"]
129
- )
130
- for response in response_iterator:
131
- logging.debug(response)
132
- permissions_set.append(response)
133
- logging.info(response)
134
-
135
- return permissions_set
90
+ client_manager = get_client_manager(region=region)
91
+ config = get_config()
92
+
93
+ with track_operation("Listing permission sets") as task_id:
94
+ try:
95
+ permission_sets = client_manager.paginate_api_call(
96
+ "sso-admin",
97
+ "list_permission_sets",
98
+ "PermissionSets",
99
+ InstanceArn=instance_arn,
100
+ PaginationConfig={
101
+ 'MaxItems': config.pagination.max_items,
102
+ 'PageSize': config.pagination.default_page_size
103
+ }
104
+ )
105
+
106
+ logger.info(f"Found {len(permission_sets)} permission sets")
107
+ return permission_sets
108
+
109
+ except Exception as e:
110
+ logger.error(f"Failed to list permission sets: {e}")
111
+ raise AWSServiceError(f"Failed to list permission sets: {e}")
112
+
113
+
114
+ def list_permission_provisioned(account_id: str, instance_arn: str, region: str) -> List[str]:
115
+ """
116
+ List permission sets provisioned to an account.
136
117
 
118
+ Args:
119
+ account_id: AWS account ID
120
+ instance_arn: SSO instance ARN
121
+ region: AWS region name
137
122
 
138
- def list_permission_provisioned(account_id, instance_arn, region):
123
+ Returns:
124
+ List of provisioned permission set ARNs
139
125
  """
140
- List permission provisioned.
141
-
142
- :param account_id:
143
- :param instance_arn:
144
- :param region:
145
- :return:
126
+ client_manager = get_client_manager(region=region)
127
+
128
+ try:
129
+ response = client_manager.call_api(
130
+ "sso-admin",
131
+ "list_permission_sets_provisioned_to_account",
132
+ InstanceArn=instance_arn,
133
+ AccountId=account_id
134
+ )
135
+
136
+ permission_sets = response.get("PermissionSets", [])
137
+ logger.debug(f"Found {len(permission_sets)} provisioned permission sets for account {account_id}")
138
+ return permission_sets
139
+
140
+ except Exception as e:
141
+ logger.error(f"Failed to list provisioned permission sets for account {account_id}: {e}")
142
+ raise AWSServiceError(f"Failed to list provisioned permission sets: {e}")
143
+
144
+
145
+ def extends_permissions_set(
146
+ permissions_sets: List[str],
147
+ store_arn: str,
148
+ region: str
149
+ ) -> Dict[str, str]:
146
150
  """
147
- l_client = client("sso-admin", region_name=region)
148
- response = l_client.list_permission_sets_provisioned_to_account(
149
- InstanceArn=instance_arn,
150
- AccountId=account_id,
151
- )
152
- logging.debug(response)
153
- return response["PermissionSets"]
151
+ Get detailed information for permission sets.
154
152
 
153
+ Args:
154
+ permissions_sets: List of permission set ARNs
155
+ store_arn: SSO instance ARN
156
+ region: AWS region name
155
157
 
156
- def extends_permissions_set(permissions_sets, store_arn, region):
158
+ Returns:
159
+ Dictionary mapping permission set ARN to name
157
160
  """
158
- List all permission set in a region.
159
-
160
- :param permissions_sets:
161
- :param store_arn:
162
- :param region:
163
- :return:
161
+ client_manager = get_client_manager(region=region)
162
+ permissions_map = {}
163
+
164
+ with track_operation(f"Getting details for {len(permissions_sets)} permission sets") as task_id:
165
+ for i, permission_set_arn in enumerate(permissions_sets):
166
+ try:
167
+ response = client_manager.call_api(
168
+ "sso-admin",
169
+ "describe_permission_set",
170
+ InstanceArn=store_arn,
171
+ PermissionSetArn=permission_set_arn
172
+ )
173
+
174
+ permission_set = response.get("PermissionSet", {})
175
+ permissions_map[permission_set_arn] = permission_set.get("Name", "Unknown")
176
+
177
+ # Update progress
178
+ if i % 5 == 0: # Update every 5 items to avoid too frequent updates
179
+ client_manager._progress_tracker.update_progress(task_id, advance=5)
180
+
181
+ except Exception as e:
182
+ logger.warning(f"Failed to describe permission set {permission_set_arn}: {e}")
183
+ permissions_map[permission_set_arn] = "Unknown"
184
+
185
+ logger.info(f"Retrieved details for {len(permissions_map)} permission sets")
186
+ return permissions_map
187
+
188
+
189
+ # Backward compatibility function
190
+ def client(service_name: str, region_name: str, profile: Optional[str] = None):
164
191
  """
165
- sso_client = client("sso-admin", region_name=region)
166
- l_permissions_set_arn_name = {}
167
- for p in permissions_sets:
168
- response = sso_client.describe_permission_set(
169
- InstanceArn=store_arn, PermissionSetArn=p
170
- )
171
-
172
- l_permissions_set_arn_name[p] = response["PermissionSet"]["Name"]
173
-
174
- logging.debug(response["PermissionSet"]["Name"])
175
- return l_permissions_set_arn_name
192
+ Backward compatibility function for existing code.
193
+
194
+ Args:
195
+ service_name: AWS service name
196
+ region_name: AWS region
197
+ profile: AWS profile (optional)
198
+
199
+ Returns:
200
+ Boto3 client instance
201
+ """
202
+ from .client_manager import client as new_client
203
+ return new_client(service_name, region_name, profile)
src/aws/exceptions.py ADDED
@@ -0,0 +1,26 @@
1
+ """Custom exceptions for AWS operations."""
2
+
3
+
4
+ class AWSError(Exception):
5
+ """Base exception for AWS-related errors."""
6
+ pass
7
+
8
+
9
+ class AWSCredentialsError(AWSError):
10
+ """Raised when AWS credentials are invalid or missing."""
11
+ pass
12
+
13
+
14
+ class AWSPermissionError(AWSError):
15
+ """Raised when AWS permissions are insufficient."""
16
+ pass
17
+
18
+
19
+ class AWSServiceError(AWSError):
20
+ """Raised when AWS service calls fail."""
21
+ pass
22
+
23
+
24
+ class AWSResourceNotFoundError(AWSError):
25
+ """Raised when AWS resources are not found."""
26
+ pass
src/config.py ADDED
@@ -0,0 +1,153 @@
1
+ """Configuration management for Reverse Diagrams."""
2
+ import os
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Optional, Dict, Any
6
+ import logging
7
+
8
+
9
+ @dataclass
10
+ class PaginationConfig:
11
+ """Configuration for AWS API pagination."""
12
+ default_page_size: int = 20
13
+ max_page_size: int = 1000
14
+ max_items: int = 10000
15
+
16
+
17
+ @dataclass
18
+ class AWSConfig:
19
+ """Configuration for AWS operations."""
20
+ timeout: int = 30
21
+ max_retries: int = 3
22
+ backoff_factor: float = 2.0
23
+
24
+ # Required permissions documentation
25
+ required_permissions: Dict[str, list] = field(default_factory=lambda: {
26
+ 'organizations': [
27
+ 'organizations:DescribeOrganization',
28
+ 'organizations:ListRoots',
29
+ 'organizations:ListOrganizationalUnitsForParent',
30
+ 'organizations:ListAccounts',
31
+ 'organizations:ListParents'
32
+ ],
33
+ 'sso-admin': [
34
+ 'sso:ListInstances',
35
+ 'sso:ListPermissionSets',
36
+ 'sso:DescribePermissionSet',
37
+ 'sso:ListAccountAssignments'
38
+ ],
39
+ 'identitystore': [
40
+ 'identitystore:ListGroups',
41
+ 'identitystore:ListUsers',
42
+ 'identitystore:ListGroupMemberships'
43
+ ]
44
+ })
45
+
46
+
47
+ @dataclass
48
+ class OutputConfig:
49
+ """Configuration for output generation."""
50
+ default_output_dir: str = "diagrams"
51
+ json_indent: int = 4
52
+ create_directories: bool = True
53
+ file_permissions: int = 0o644
54
+
55
+ # Supported output formats
56
+ supported_formats: list = field(default_factory=lambda: ['png', 'svg', 'pdf'])
57
+
58
+
59
+ @dataclass
60
+ class LoggingConfig:
61
+ """Configuration for logging."""
62
+ level: str = "INFO"
63
+ format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
64
+ file_path: Optional[Path] = None
65
+ max_file_size: int = 10 * 1024 * 1024 # 10MB
66
+ backup_count: int = 5
67
+
68
+
69
+ @dataclass
70
+ class Config:
71
+ """Main configuration class."""
72
+ pagination: PaginationConfig = field(default_factory=PaginationConfig)
73
+ aws: AWSConfig = field(default_factory=AWSConfig)
74
+ output: OutputConfig = field(default_factory=OutputConfig)
75
+ logging: LoggingConfig = field(default_factory=LoggingConfig)
76
+
77
+ # Performance settings
78
+ max_concurrent_workers: int = 5
79
+ enable_caching: bool = True
80
+ cache_ttl_hours: int = 1
81
+
82
+ @classmethod
83
+ def from_env(cls) -> 'Config':
84
+ """Create configuration from environment variables."""
85
+ config = cls()
86
+
87
+ # AWS configuration from environment
88
+ if os.getenv('AWS_TIMEOUT'):
89
+ config.aws.timeout = int(os.getenv('AWS_TIMEOUT'))
90
+ if os.getenv('AWS_MAX_RETRIES'):
91
+ config.aws.max_retries = int(os.getenv('AWS_MAX_RETRIES'))
92
+
93
+ # Output configuration from environment
94
+ if os.getenv('REVERSE_DIAGRAMS_OUTPUT_DIR'):
95
+ config.output.default_output_dir = os.getenv('REVERSE_DIAGRAMS_OUTPUT_DIR')
96
+
97
+ # Logging configuration from environment
98
+ if os.getenv('LOG_LEVEL'):
99
+ config.logging.level = os.getenv('LOG_LEVEL').upper()
100
+ if os.getenv('LOG_FILE'):
101
+ config.logging.file_path = Path(os.getenv('LOG_FILE'))
102
+
103
+ # Performance settings from environment
104
+ if os.getenv('MAX_WORKERS'):
105
+ config.max_concurrent_workers = int(os.getenv('MAX_WORKERS'))
106
+ if os.getenv('ENABLE_CACHING'):
107
+ config.enable_caching = os.getenv('ENABLE_CACHING').lower() == 'true'
108
+
109
+ return config
110
+
111
+ def setup_logging(self):
112
+ """Setup logging based on configuration."""
113
+ handlers = [logging.StreamHandler()]
114
+
115
+ if self.logging.file_path:
116
+ from logging.handlers import RotatingFileHandler
117
+ file_handler = RotatingFileHandler(
118
+ self.logging.file_path,
119
+ maxBytes=self.logging.max_file_size,
120
+ backupCount=self.logging.backup_count
121
+ )
122
+ handlers.append(file_handler)
123
+
124
+ logging.basicConfig(
125
+ level=getattr(logging, self.logging.level),
126
+ format=self.logging.format,
127
+ handlers=handlers
128
+ )
129
+
130
+ # Reduce noise from AWS SDK
131
+ logging.getLogger('boto3').setLevel(logging.WARNING)
132
+ logging.getLogger('botocore').setLevel(logging.WARNING)
133
+ logging.getLogger('urllib3').setLevel(logging.WARNING)
134
+
135
+
136
+ # Global configuration instance
137
+ _config: Optional[Config] = None
138
+
139
+
140
+ def get_config() -> Config:
141
+ """Get global configuration instance."""
142
+ global _config
143
+ if _config is None:
144
+ _config = Config.from_env()
145
+ _config.setup_logging()
146
+ return _config
147
+
148
+
149
+ def set_config(config: Config):
150
+ """Set global configuration instance."""
151
+ global _config
152
+ _config = config
153
+ _config.setup_logging()