cloud-governance 1.1.369__py3-none-any.whl → 1.1.371__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.
@@ -5,10 +5,13 @@ import boto3
5
5
  from cloud_governance.common.clouds.aws.utils.common_methods import get_boto3_client
6
6
  from cloud_governance.common.clouds.aws.utils.utils import Utils
7
7
  from cloud_governance.common.logger.init_logger import logger
8
+ from datetime import datetime, timezone
8
9
 
9
10
 
10
11
  class IAMOperations:
11
12
 
13
+ ACCESS_KEY_LABEL_MAP = {"access key 1": 0, "access key 2": 1}
14
+
12
15
  def __init__(self, iam_client=None):
13
16
  self.iam_client = iam_client if iam_client else get_boto3_client('iam')
14
17
  self.utils = Utils()
@@ -158,3 +161,157 @@ class IAMOperations:
158
161
  return True
159
162
  except Exception as err:
160
163
  raise err
164
+
165
+ def tag_user(self, user_name: str, tags: list):
166
+ """
167
+ This method tags the IAM user.
168
+ :param user_name: The name of the IAM user to tag.
169
+ :param tags: A list of tags to associate with the user.
170
+ :return: True if tagging is successful, otherwise raises an exception.
171
+ """
172
+ try:
173
+ self.iam_client.tag_user(UserName=user_name, Tags=tags)
174
+ return True
175
+ except Exception as err:
176
+ raise err
177
+
178
+ def get_iam_users_access_keys(self):
179
+ """
180
+ Retrieves IAM users and summarizes:
181
+ - Access key status (active/inactive)
182
+ - Access key age in days
183
+ - Access key last used in days (or "N/A" if never used)
184
+ - Tags (as a list of dictionaries)
185
+ - Most recent key usage: last_activity_days
186
+ - IAM client region (global context, since IAM is non-regional)
187
+ - IAM user unique ID: ResourceId
188
+
189
+ Returns:
190
+ dict: {
191
+ "username": {
192
+ "Access key 1": [status, age_days, last_used_days],
193
+ "Access key 2": [...],
194
+ "last_activity_days": int or "N/A",
195
+ "tags": [{"Key": "tag_key", "Value": "tag_value"}, ...],
196
+ "region": "us-east-1",
197
+ "ResourceId": "AIDAEXAMPLEUSERID"
198
+ },
199
+ ...
200
+ }
201
+ """
202
+ result = {}
203
+ now = datetime.now(timezone.utc)
204
+ region_name = self.iam_client.meta.region_name or "global"
205
+
206
+ paginator = self.iam_client.get_paginator('list_users')
207
+ for page in paginator.paginate():
208
+ for user in page['Users']:
209
+ username = user['UserName']
210
+ result[username] = {}
211
+ # Access keys
212
+ access_keys = self.iam_client.list_access_keys(UserName=username)['AccessKeyMetadata']
213
+ for idx, key in enumerate(access_keys, start=1):
214
+ label = f"Access key {idx}"
215
+ status = key['Status'].lower()
216
+ age_days = (now - key['CreateDate']).days
217
+
218
+ # Get access key last used
219
+ try:
220
+ response = self.iam_client.get_access_key_last_used(AccessKeyId=key['AccessKeyId'])
221
+ last_used_date = response.get('AccessKeyLastUsed', {}).get('LastUsedDate')
222
+ if last_used_date:
223
+ last_used_days = (now - last_used_date).days
224
+ else:
225
+ last_used_days = "N/A"
226
+ except Exception:
227
+ last_used_days = "N/A"
228
+
229
+ result[username][label] = {'label': label, 'status': status, 'age_days': age_days, 'last_activity_days': last_used_days if last_used_days is not None else "N/A"}
230
+
231
+ # Tags as list of dicts
232
+ try:
233
+ tag_response = self.iam_client.list_user_tags(UserName=username)
234
+ tags = tag_response.get('Tags', [])
235
+ except Exception:
236
+ tags = []
237
+
238
+ result[username]["tags"] = tags
239
+ result[username]["region"] = region_name
240
+ result[username]["ResourceId"] = user.get('UserId') # <-- Unique ID
241
+
242
+ return result
243
+
244
+ def has_active_access_keys(self, username: str, access_key_label: str = None) -> bool:
245
+ """
246
+ Checks if the given IAM user has any active access keys.
247
+ Optionally filters by access key label ("Access Key 1" or "Access Key 2").
248
+
249
+ Args:
250
+ username (str): IAM user name
251
+ access_key_label (str): Label to filter access keys ("Access Key 1"/"Access Key 2")
252
+
253
+ Returns:
254
+ bool: True if any access key is active (and matches the label if provided), False otherwise
255
+ """
256
+ try:
257
+ keys = self.iam_client.list_access_keys(UserName=username)['AccessKeyMetadata']
258
+ except Exception as e:
259
+ logger.error(f"Failed to list access keys for user '{username}': {e}")
260
+ return False
261
+
262
+ # Sort keys by CreateDate ascending (oldest first)
263
+ keys.sort(key=lambda k: k['CreateDate'])
264
+
265
+ if access_key_label:
266
+ idx = self.ACCESS_KEY_LABEL_MAP.get(access_key_label.lower())
267
+ if idx is None or idx >= len(keys):
268
+ return False
269
+ return keys[idx].get('Status') == 'Active'
270
+
271
+ return any(k.get('Status') == 'Active' for k in keys)
272
+
273
+ def deactivate_user_access_key(self, username: str, **kwargs):
274
+ """
275
+ Deactivates the specified access key for the given IAM user.
276
+
277
+ Args:
278
+ username (str): IAM user name
279
+ access_key_label (str): Access Key 1 or Access Key 2 (case-insensitive)
280
+ """
281
+ access_key_label = kwargs.get('access_key_label', '').lower()
282
+ if not access_key_label:
283
+ logger.warning("No access key label provided for deactivation.")
284
+ return
285
+
286
+ try:
287
+ access_keys = self.iam_client.list_access_keys(UserName=username)['AccessKeyMetadata']
288
+ except Exception as e:
289
+ logger.error(f"Failed to list access keys for user '{username}': {e}")
290
+ return
291
+
292
+ # Sort keys by CreateDate ascending (oldest first) for consistent indexing
293
+ access_keys.sort(key=lambda k: k['CreateDate'])
294
+
295
+ idx = self.ACCESS_KEY_LABEL_MAP.get(access_key_label)
296
+ if idx is None or idx >= len(access_keys):
297
+ logger.warning(f"Access key label '{access_key_label}' not found for user '{username}'")
298
+ return
299
+
300
+ key_to_deactivate = access_keys[idx]
301
+ access_key_id = key_to_deactivate['AccessKeyId']
302
+ current_status = key_to_deactivate['Status'].lower()
303
+
304
+ if current_status == 'active':
305
+ try:
306
+ self.iam_client.update_access_key(
307
+ UserName=username,
308
+ AccessKeyId=access_key_id,
309
+ Status='Inactive'
310
+ )
311
+ logger.info(f"Access key '{access_key_id}' deactivated for user '{username}'")
312
+ except Exception as e:
313
+ logger.error(f"Failed to deactivate access key '{access_key_id}' for user '{username}': {e}")
314
+ else:
315
+ logger.info(f"Access key '{access_key_id}' is already inactive for user '{username}'")
316
+
317
+ logger.info(f"Access key deactivation processed for user '{username}'.")
@@ -37,6 +37,8 @@ class PolicyEsMetaData(dict):
37
37
  launch_time: str = ''
38
38
  running_days: int = ''
39
39
  create_date: str = ''
40
+ age_days: int = ''
41
+ last_activity_days: int = ''
40
42
 
41
43
  def __post_init__(self):
42
44
  """
@@ -22,6 +22,8 @@ INSTANCE_IDLE_NETWORK_OUT_KILO_BYTES = 5 # In KiB
22
22
  EC2_NAMESPACE = 'AWS/EC2'
23
23
  CLOUDWATCH_METRICS_AVAILABLE_DAYS = 14
24
24
  AWS_DEFAULT_GLOBAL_REGION = 'us-east-1'
25
+ UNUSED_ACCESS_KEY_DAYS = 90
26
+ UNUSED_ACCESS_KEY_MAX_DAY = 1000
25
27
 
26
28
  # X86 to Graviton
27
29
  GRAVITON_MAPPINGS = {
@@ -98,7 +98,7 @@ class EnvironmentVariables:
98
98
  'ip_unattached', 'unused_nat_gateway',
99
99
  'instance_idle',
100
100
  'ec2_stop', 'ebs_in_use', 'database_idle',
101
- 's3_inactive',
101
+ 's3_inactive', 'unused_access_key',
102
102
  'empty_roles',
103
103
  'zombie_snapshots', 'skipped_resources',
104
104
  'monthly_report', 'optimize_resources_report']
@@ -283,6 +283,8 @@ class EnvironmentVariables:
283
283
  self._environment_variables_dict['POLICIES_TO_ALERT'] = literal_eval(
284
284
  EnvironmentVariables.get_env('POLICIES_TO_ALERT', '[]'))
285
285
  self._environment_variables_dict['ADMIN_MAIL_LIST'] = EnvironmentVariables.get_env('ADMIN_MAIL_LIST', '')
286
+ self._environment_variables_dict['SKIP_POLICIES_ALERT'] = literal_eval(
287
+ EnvironmentVariables.get_env('SKIP_POLICIES_ALERT', "['']"))
286
288
  if self._environment_variables_dict.get('policy') in ['send_aggregated_alerts', 'cloudability_cost_reports']:
287
289
  self._environment_variables_dict['COMMON_POLICIES'] = True
288
290
  # CRO -- Cloud Resource Orch
@@ -42,7 +42,7 @@ class MainOperations:
42
42
  # @Todo support for all the aws policies, currently supports ec2_run as urgent requirement
43
43
  if self._policy in policies and self._policy in ["instance_run", "unattached_volume", "cluster_run",
44
44
  "ip_unattached", "unused_nat_gateway", "instance_idle",
45
- "zombie_snapshots", "database_idle", "s3_inactive",
45
+ "zombie_snapshots", "database_idle", "s3_inactive", "unused_access_key",
46
46
  "empty_roles", "tag_resources", "cost_usage_reports"]:
47
47
  source = policy_type
48
48
  if Utils.equal_ignore_case(policy_type, self._public_cloud_name):
@@ -37,7 +37,8 @@ class MonthlyReport:
37
37
  'ip_unattached': 'Delete all the elastic_ips that are unused',
38
38
  'unused_nat_gateway': ' Delete all unused nat gateways',
39
39
  'zombie_snapshots': 'Delete all the snapshots which the AMI does not use',
40
- 's3_inactive': 'Delete the empty buckets which don’t have any content.',
40
+ 's3_inactive': 'Delete the empty buckets which don’t have any content',
41
+ 'unused_access_key': 'Deactivate user access keys that are still active but have not been used',
41
42
  'empty_roles': 'Delete the empty role which does\'t have any policies',
42
43
  'zombie_cluster_resource': 'Delete up the cluster resources which are not deleted while cleaning the cluster'
43
44
  }
@@ -0,0 +1,55 @@
1
+ from cloud_governance.common.utils.configs import UNUSED_ACCESS_KEY_DAYS, UNUSED_ACCESS_KEY_MAX_DAY
2
+ from cloud_governance.policy.helpers.aws.aws_policy_operations import AWSPolicyOperations
3
+
4
+
5
+ class UnusedAccessKey(AWSPolicyOperations):
6
+ RESOURCE_ACTION = "DeActivate"
7
+
8
+ def __init__(self):
9
+ super().__init__()
10
+
11
+ def run_policy_operations(self):
12
+ """
13
+ This method returns a list of users with at least one active access key whose last used date is greater than UNUSED_ACCESS_KEY_DAYS
14
+ :return:
15
+ :rtype:
16
+ """
17
+ unused_access_keys = []
18
+ iam_users_access_keys = self._get_iam_users_access_keys()
19
+ for username, user_data in iam_users_access_keys.items():
20
+ for access_key_label, access_key_data in user_data.items():
21
+ if 'access key' in access_key_label.lower():
22
+ last_activity_days = access_key_data['last_activity_days']
23
+ age_days = access_key_data['age_days']
24
+ # if access key last_activity_days is "N/A", use age_days
25
+ if last_activity_days == "N/A":
26
+ last_activity_days = age_days
27
+ region = user_data['region']
28
+ user_name = username
29
+ tags = user_data.get('Tags', [])
30
+ cleanup_result = False
31
+ cleanup_days = 0
32
+ if int(last_activity_days) >= UNUSED_ACCESS_KEY_DAYS and self._has_active_access_keys(user_name, access_key_label) and self.get_skip_policy_value(tags=tags) not in ('NOTDELETE', 'SKIP'):
33
+ cleanup_days = self.get_clean_up_days_count(tags=tags)
34
+ cleanup_result = self.verify_and_delete_resource(resource_id=user_name, tags=tags,
35
+ clean_up_days=cleanup_days, access_key_label=access_key_label)
36
+ resource_data = self._get_es_schema(resource_id=user_name,
37
+ user=self.get_tag_name_from_tags(tags=tags, tag_name='User'),
38
+ skip_policy=self.get_skip_policy_value(tags=tags),
39
+ cleanup_days=cleanup_days,
40
+ dry_run=self._dry_run,
41
+ name=user_name,
42
+ region=region,
43
+ cleanup_result=str(cleanup_result),
44
+ resource_action=self.RESOURCE_ACTION,
45
+ cloud_name=self._cloud_name,
46
+ resource_type='UnusedAccessKey',
47
+ resource_state='Active',
48
+ age_days=age_days,
49
+ last_activity_days=last_activity_days,
50
+ unit_price=0)
51
+ unused_access_keys.append(resource_data)
52
+ if not cleanup_result:
53
+ self.update_resource_day_count_tag(resource_id=user_name, cleanup_days=cleanup_days, tags=tags)
54
+
55
+ return unused_access_keys
@@ -20,6 +20,7 @@ class SendAggregatedAlerts:
20
20
  self.__mail_to = self.__environment_variables.get('EMAIL_TO') # testing purposes
21
21
  self.__mail_cc = self.__environment_variables.get('EMAIL_CC', [])
22
22
  self.__alert_dry_run = self.__environment_variables.get('ALERT_DRY_RUN')
23
+ self.__skip_policies_alert = self.__environment_variables.get('SKIP_POLICIES_ALERT')
23
24
  self.__mail_message = MailMessage()
24
25
  self.__postfix = Postfix()
25
26
  self.__es_operations = ElasticSearchOperations()
@@ -55,7 +56,7 @@ class SendAggregatedAlerts:
55
56
  "ebs_in_use",
56
57
  "instance_run", "cluster_run", "optimize_resource_report",
57
58
  "optimize_resources_report", "skipped_resources"
58
- ]
59
+ ] + self.__skip_policies_alert
59
60
  }
60
61
  }
61
62
  ],
@@ -162,7 +163,8 @@ class SendAggregatedAlerts:
162
163
  if days >= days_to_take_action:
163
164
  delete_date = datetime.utcnow().date().__str__()
164
165
  alert_user = True
165
- if record.get('policy') in ['empty_roles', 's3_inactive']:
166
+ # Cross region policies
167
+ if record.get('policy') in ['empty_roles', 's3_inactive', 'unused_access_key']:
166
168
  record['RegionName'] = 'us-east-1'
167
169
  if Utils.equal_ignore_case(dry_run, 'yes'):
168
170
  record['DeleteDate'] = 'dry_run=yes'
@@ -111,7 +111,7 @@ class AbstractPolicyOperations(ABC):
111
111
  return 'NA'
112
112
 
113
113
  @abstractmethod
114
- def _delete_resource(self, resource_id: str):
114
+ def _delete_resource(self, resource_id: str, **kwargs):
115
115
  """
116
116
  This method deletes the resource
117
117
  :param resource_id:
@@ -144,7 +144,7 @@ class AbstractPolicyOperations(ABC):
144
144
  :rtype:
145
145
  """
146
146
  if self._resource_id == resource_id and self._force_delete and self._dry_run == 'no':
147
- self._delete_resource(resource_id=resource_id)
147
+ self._delete_resource(resource_id=resource_id, **kwargs)
148
148
  return True
149
149
  if not days_to_delete_resource:
150
150
  days_to_delete_resource = self._days_to_take_action
@@ -157,7 +157,7 @@ class AbstractPolicyOperations(ABC):
157
157
  if clean_up_days >= days_to_delete_resource:
158
158
  if self._dry_run == 'no':
159
159
  if self.get_skip_policy_value(tags=tags) not in ('NOTDELETE', 'SKIP'):
160
- self._delete_resource(resource_id=resource_id)
160
+ self._delete_resource(resource_id=resource_id, **kwargs)
161
161
  cleanup_resources = True
162
162
  return cleanup_resources
163
163
 
@@ -21,7 +21,7 @@ class AWSPolicyOperations(AbstractPolicyOperations):
21
21
  self.policy_name = self._environment_variables_dict.get('policy')
22
22
  self._cloud_name = 'AWS'
23
23
  self._ec2_client = get_boto3_client(client='ec2', region_name=self._region)
24
- self._s3_client = get_boto3_client('s3', region_name=self._region)
24
+ self._s3_client = get_boto3_client(client='s3', region_name=self._region)
25
25
  self._iam_operations = IAMOperations()
26
26
  self._rds_operations = RDSOperations(region_name=self._region)
27
27
  self._s3operations = S3Operations(region_name=self._region)
@@ -46,7 +46,7 @@ class AWSPolicyOperations(AbstractPolicyOperations):
46
46
  return tag.get('Value').strip()
47
47
  return ''
48
48
 
49
- def _delete_resource(self, resource_id: str):
49
+ def _delete_resource(self, resource_id: str, **kwargs):
50
50
  """
51
51
  This method deletes the resource by verifying the policy
52
52
  :param resource_id:
@@ -58,6 +58,8 @@ class AWSPolicyOperations(AbstractPolicyOperations):
58
58
  try:
59
59
  if self._policy == 's3_inactive':
60
60
  self._s3_client.delete_bucket(Bucket=resource_id)
61
+ elif self._policy == 'unused_access_key':
62
+ self._iam_operations.deactivate_user_access_key(username=resource_id, **kwargs)
61
63
  elif self._policy == 'empty_roles':
62
64
  response = self._iam_operations.delete_role(role_name=resource_id)
63
65
  elif self._policy == 'unattached_volume':
@@ -147,6 +149,8 @@ class AWSPolicyOperations(AbstractPolicyOperations):
147
149
  try:
148
150
  if self._policy == 's3_inactive':
149
151
  self._s3_client.put_bucket_tagging(Bucket=resource_id, Tagging={'TagSet': tags})
152
+ elif self._policy == 'unused_access_key':
153
+ self._iam_operations.tag_user(user_name=resource_id, tags=tags)
150
154
  elif self._policy == 'empty_roles':
151
155
  self._iam_operations.tag_role(role_name=resource_id, tags=tags)
152
156
  elif self._policy in ('ip_unattached', 'unused_nat_gateway', 'zombie_snapshots', 'unattached_volume',
@@ -196,6 +200,29 @@ class AWSPolicyOperations(AbstractPolicyOperations):
196
200
  volumes = self._ec2_operations.get_volumes(**kwargs)
197
201
  return volumes
198
202
 
203
+ def _get_iam_users_access_keys(self) -> dict:
204
+ """
205
+ This method returns a list of user access keys with their age in days, last used time in days, user tags, and more.
206
+ :return: list of user access keys
207
+ """
208
+ return self._iam_operations.get_iam_users_access_keys()
209
+
210
+ def _has_active_access_keys(self, user_name: str, access_key_label: str) -> bool:
211
+ """
212
+ This method checks if the given IAM user has any active access keys.
213
+ :return:
214
+ :rtype:
215
+ """
216
+ return self._iam_operations.has_active_access_keys(username=user_name, access_key_label=access_key_label)
217
+
218
+ def _deactivate_access_key(self, user_name: str, access_key_label: str) -> bool:
219
+ """
220
+ This method checks if the given IAM user has any active access keys.
221
+ :return:
222
+ :rtype:
223
+ """
224
+ return self._iam_operations.deactivate_user_access_key(username=user_name, access_key_label=access_key_label)
225
+
199
226
  def _get_active_cluster_ids(self):
200
227
  """
201
228
  This method returns the active cluster id's
@@ -45,7 +45,7 @@ class ZombieNonClusterPolicies(NonClusterZombiePolicy):
45
45
  logger.error('ElasticSearch host is not pingable, Please check ')
46
46
 
47
47
  if self._policy_output:
48
- # if self._policy not in ('ec2_idle', 'ebs_in_use', 'ec2_run', 's3_inactive', 'zombie_snapshots', 'nat_gateway_unused'):
48
+ # if self._policy not in ('ec2_idle', 'ebs_in_use', 'ec2_run', 's3_inactive', 'unused_access_key', 'zombie_snapshots', 'nat_gateway_unused'):
49
49
  # beautify_data = self._beautify_upload_data(upload_resource_data=response)
50
50
  # policy_result = {'count': len(beautify_data), self._policy: beautify_data}
51
51
  logger.info(policy_result)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloud-governance
3
- Version: 1.1.369
3
+ Version: 1.1.371
4
4
  Summary: Cloud Governance Tool
5
5
  Home-page: https://github.com/redhat-performance/cloud-governance
6
6
  Author: Red Hat
@@ -103,6 +103,7 @@ List of Policies:
103
103
  - zombie_snapshots
104
104
  - unused_nat_gateway
105
105
  - s3_inactive
106
+ - unused_access_key
106
107
  - empty_roles
107
108
  - tag_resources
108
109
  - tag_iam_user
@@ -51,7 +51,7 @@ cloud_governance/common/clouds/aws/dynamodb/dynamodb_operations.py,sha256=eeV3Yg
51
51
  cloud_governance/common/clouds/aws/ec2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  cloud_governance/common/clouds/aws/ec2/ec2_operations.py,sha256=0ibMvf7nqyK9MCRhxrg11xnOPELqsZIQsZUgd6_neKc,24680
53
53
  cloud_governance/common/clouds/aws/iam/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- cloud_governance/common/clouds/aws/iam/iam_operations.py,sha256=ngVh19oIse1UjzvUkImYzs3xBFt74LhdUGbOhI58ZMA,4843
54
+ cloud_governance/common/clouds/aws/iam/iam_operations.py,sha256=QXRevArr3OjSOIDhAAc61iZyZZSMGxI97ezySOgdqv8,11485
55
55
  cloud_governance/common/clouds/aws/price/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  cloud_governance/common/clouds/aws/price/price.py,sha256=Ju76WniNTVujQPi6QFRgLt6Jd8gsxL2FOnN7c1et5F0,6403
57
57
  cloud_governance/common/clouds/aws/price/resources_pricing.py,sha256=k-hucUvxpZfF126wLQQwJt3BzNQiOoSU1x6gjIQ_4cM,6253
@@ -111,7 +111,7 @@ cloud_governance/common/elasticsearch/elasticsearch_exceptions.py,sha256=6rwxbRY
111
111
  cloud_governance/common/elasticsearch/elasticsearch_operations.py,sha256=WsAAjjCh-vX6K25SzQRP-rvPJ59RJ4dAtcXHCtmRUOQ,16529
112
112
  cloud_governance/common/elasticsearch/modals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
113
  cloud_governance/common/elasticsearch/modals/cost_usage_reports_data.py,sha256=xDxGIxJwhOtSh2VOnYx7XShRF90WPPlaQrfVm3fLQr8,1396
114
- cloud_governance/common/elasticsearch/modals/policy_es_data.py,sha256=6ydpSP_3e1OgyxM9IucohU8wSl0D2quisbmVlkM6WOA,1436
114
+ cloud_governance/common/elasticsearch/modals/policy_es_data.py,sha256=ZyjHakWQRs3JeL-apxv8fMP9GhK5bR-HvR_KzSlAc5s,1492
115
115
  cloud_governance/common/google_drive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
116
  cloud_governance/common/google_drive/gcp_operations.py,sha256=WtEILQlD1XQCGx93Yuwgf1HyS90ZzS_6z1Nu4aKDEvg,1137
117
117
  cloud_governance/common/google_drive/google_drive_operations.py,sha256=U1JWNWusVlsm8xd81UF9Xgc0ZONyV4ENCkmN_W20DCw,8461
@@ -139,18 +139,18 @@ cloud_governance/common/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
139
139
  cloud_governance/common/tool/tool.py,sha256=EG4MGXzg6k6kLFff_pdghD--QBykgAZh0OPA7g8EPog,1241
140
140
  cloud_governance/common/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
141
  cloud_governance/common/utils/api_requests.py,sha256=WwkvVAhJKEpU0JWz0Df8yN2W7iN7Q7tieJM813_DZ9c,674
142
- cloud_governance/common/utils/configs.py,sha256=8ry4UQT9s_GUivhrCdwS0vOWYN6OJIHDFzbVpi3BYpY,1194
142
+ cloud_governance/common/utils/configs.py,sha256=shFxWt0Kc-GwzcZKYCkHm058ujwdTxn42mMBcXO0egE,1255
143
143
  cloud_governance/common/utils/json_datetime_encoder.py,sha256=_-jzRTe0UqAKTn2E9qaU8SYIxHUoRA5ElWuVA0Y54Xw,338
144
144
  cloud_governance/common/utils/utils.py,sha256=ZUsi4ax2XhDIV-EQ5kJt5Ppd72kmm2psqcg1cNDZrvc,4349
145
145
  cloud_governance/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
- cloud_governance/main/environment_variables.py,sha256=0Ss2FEBoJ9pJOwazPpe56BgPDLSCTu5k3lwqxTTznlU,29161
146
+ cloud_governance/main/environment_variables.py,sha256=y5oSVmTOaX1KyrHbbdH0RVSPPVmEsa9JtQwUzjp1RPU,29335
147
147
  cloud_governance/main/environment_variables_exceptions.py,sha256=UR0Ith0P0oshsDZdJRlRq8ZUTt0h8jFvUtrnP4m4AIY,437
148
148
  cloud_governance/main/es_uploader.py,sha256=6Ify5CS2NtUF1xXZ-rMwpYxVzDKfEZhv2vogWFltt98,10656
149
149
  cloud_governance/main/main.py,sha256=4TBiCBO_1z8KpGXb8brPKBmv3jHBl8fl_-Sb80ctYno,18880
150
150
  cloud_governance/main/main_common_operations.py,sha256=YbBJF6Smk3YKhEibnn-fIWu1oKP0pSGIM1WnSXFcBuo,366
151
151
  cloud_governance/main/run_cloud_resource_orchestration.py,sha256=Jo7-KDqxrIo8uioyTFCohUHse4uqdEt2ZFnHlX2u57g,776
152
152
  cloud_governance/main/main_oerations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
153
- cloud_governance/main/main_oerations/main_operations.py,sha256=Jgt2G7X1WB_zzYsmii5sS50kSQTaqEMXTjfi-LqbT4g,2505
153
+ cloud_governance/main/main_oerations/main_operations.py,sha256=iHyUDly2p0fz_gWbpeVNfiqO1f0xk09UKlnFa6sQa8U,2526
154
154
  cloud_governance/policy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
155
155
  cloud_governance/policy/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
156
156
  cloud_governance/policy/aws/cost_billing_reports.py,sha256=pGzCl-BRWU5ZTM-tn34munzmetODCyeT7EtuM7XCO4A,7429
@@ -161,11 +161,12 @@ cloud_governance/policy/aws/ebs_in_use.py,sha256=7vGV2qobV14rzC7tJnJiafC3oAhnY_T
161
161
  cloud_governance/policy/aws/ec2_stop.py,sha256=FXQNkHmiMCEw6Pz6CalIIVYFRsV24RT5TewqFazyP8M,9641
162
162
  cloud_governance/policy/aws/empty_roles.py,sha256=EhsepFRbjU82PjJQ1hADqBM_RO1Ukr-Q4DZAz3bM4ho,3155
163
163
  cloud_governance/policy/aws/ip_unattached.py,sha256=OnHlTIKSgyxwdAgU9jBApdvDQpzHgECjSodN6KoXWqU,3176
164
- cloud_governance/policy/aws/monthly_report.py,sha256=jxdObuFEWPobRFtVKgo-sMT4gmMC_KS9gkPVF8ZqjpY,6722
164
+ cloud_governance/policy/aws/monthly_report.py,sha256=cSeOfdU5u2BMUR1RmxTrrxpMSlOCG0oqKUeSqwdU_jY,6830
165
165
  cloud_governance/policy/aws/optimize_resources_report.py,sha256=zG7w8KHF7Z25jxYGgDadyXp0jcxSREsRCOmQP8lZMNc,6003
166
166
  cloud_governance/policy/aws/s3_inactive.py,sha256=NXUUGtJhpqohaWYczSebw-q0Q7RKg3XMMj_h7xfGhQ0,2839
167
167
  cloud_governance/policy/aws/skipped_resources.py,sha256=D0kbt9dg6Bkl5PgGaqimWvLct6D-JOnerzJ0FkWzGFc,5679
168
168
  cloud_governance/policy/aws/spot_savings_analysis.py,sha256=lGG5qtz8pr7xjLo5BrtVSHGTz928MLwYPbSCaDfpTes,5513
169
+ cloud_governance/policy/aws/unused_access_key.py,sha256=62_JaQMZLgnmYck0Brp8CTS1CvyRhwcNTXYmFjgWMoY,3542
169
170
  cloud_governance/policy/aws/zombie_cluster_resource.py,sha256=pwE8MypeqQfSQs--U1hbk5nk1M6x0R_NLs0x-wmOIjI,59626
170
171
  cloud_governance/policy/aws/zombie_snapshots.py,sha256=V48cq4GCG2z-MRwUSE4b5wQcGeI_T1Ah99SiHlTwkvY,3834
171
172
  cloud_governance/policy/aws/cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -186,14 +187,14 @@ cloud_governance/policy/azure/cleanup/unattached_volume.py,sha256=MXX_R7bWAYy-Yj
186
187
  cloud_governance/policy/azure/cleanup/unused_nat_gateway.py,sha256=aJVYHtERfp23kiI0VAkqqu2tDzZzyYZMokrJFfUVpuw,3882
187
188
  cloud_governance/policy/common_policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
188
189
  cloud_governance/policy/common_policies/cloudability_cost_reports.py,sha256=ivmYvkobi8O4kYVuZNzGTJSqTenqdwb3VOc4Nv2bfh0,9540
189
- cloud_governance/policy/common_policies/send_aggregated_alerts.py,sha256=Ip2LiDFzN4yed7HxsFpKm4qIoKSOQ9U8Le96uFC3Bnk,8750
190
+ cloud_governance/policy/common_policies/send_aggregated_alerts.py,sha256=ejGGQCpi2NJ5qC0c9kzdrKzcLD5H5Ogc6nL8UYs6yS8,8933
190
191
  cloud_governance/policy/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
192
  cloud_governance/policy/gcp/cost_billing_reports.py,sha256=6de0r9I-Yr9GpEqHJxYCAdTUkmgNrzS-7ZthcVBL1m0,14908
192
193
  cloud_governance/policy/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
193
- cloud_governance/policy/helpers/abstract_policy_operations.py,sha256=l_io7Cf1DKBt-gwdDIboDrRx5yyrRm8hBYz9VzhP0Jk,10660
194
+ cloud_governance/policy/helpers/abstract_policy_operations.py,sha256=0Fr3OzeI5PA_D7hLCy-mSHhMyogCKX892yh32lbieGk,10690
194
195
  cloud_governance/policy/helpers/cloudability_operations.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
196
  cloud_governance/policy/helpers/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
- cloud_governance/policy/helpers/aws/aws_policy_operations.py,sha256=PGILBIllxUU9wYSl3qsODu03rKism4ptwpy5wsyQckg,15540
197
+ cloud_governance/policy/helpers/aws/aws_policy_operations.py,sha256=t_-t15U7xcz-jRobgvXTX_eDQM-13F57PjEb5W2YFpU,16819
197
198
  cloud_governance/policy/helpers/azure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
199
  cloud_governance/policy/helpers/azure/azure_policy_operations.py,sha256=9V8_oU3ZnkGdwv-zDzoJZUdG0d_ZrrH4t_VmDREXhaE,10479
199
200
  cloud_governance/policy/ibm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -236,7 +237,7 @@ cloud_governance/policy/policy_operations/aws/zombie_cluster/validate_zombies.py
236
237
  cloud_governance/policy/policy_operations/aws/zombie_cluster/zombie_cluster_common_methods.py,sha256=VqXk8A0OLZOxBm422kb-n5zfM8DY4FYdkwZO91xwRxQ,14791
237
238
  cloud_governance/policy/policy_operations/aws/zombie_non_cluster/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
238
239
  cloud_governance/policy/policy_operations/aws/zombie_non_cluster/run_zombie_non_cluster_policies.py,sha256=jWWCFG7b3JEc0vcoeIHfiJqi1ptkLeNtVWN40XUsWGw,21045
239
- cloud_governance/policy/policy_operations/aws/zombie_non_cluster/zombie_non_cluster_polices.py,sha256=orKRzsD3orxp3sxz7Xa6kedlBiC3p2FZHhgoSVzPZhg,2929
240
+ cloud_governance/policy/policy_operations/aws/zombie_non_cluster/zombie_non_cluster_polices.py,sha256=EmRteD5_K4JwHHPC1j25lchgKOb3gnrY4Ge9c6xlqFk,2950
240
241
  cloud_governance/policy/policy_operations/azure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
241
242
  cloud_governance/policy/policy_operations/azure/azure_policy_runner.py,sha256=sHvdVuZCY-FlIwe843aBtx99aC6gGNXM7r6s7Uv3xWk,1129
242
243
  cloud_governance/policy/policy_operations/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -263,8 +264,8 @@ cloud_governance/policy/policy_runners/elasticsearch/__init__.py,sha256=47DEQpj8
263
264
  cloud_governance/policy/policy_runners/elasticsearch/upload_elastic_search.py,sha256=pOwUJWXjJbyTy8iv3Ap8xJGnqQe-5lZgoR8-vGfAVos,1881
264
265
  cloud_governance/policy/policy_runners/ibm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
265
266
  cloud_governance/policy/policy_runners/ibm/policy_runner.py,sha256=V0E_f7F3hXit0aSq4BlfX1Jd4vjR2NEvOWsJ5upvZ4o,1302
266
- cloud_governance-1.1.369.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
267
- cloud_governance-1.1.369.dist-info/METADATA,sha256=Uv7sfKliyKYPIUvh03-b-TwNP4mLUEqFqXdC5xUwTeI,11364
268
- cloud_governance-1.1.369.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
269
- cloud_governance-1.1.369.dist-info/top_level.txt,sha256=jfB1fgj7jvx3YZkZA4G6hFeS1RHO7J7XtnbjuMNMRww,17
270
- cloud_governance-1.1.369.dist-info/RECORD,,
267
+ cloud_governance-1.1.371.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
268
+ cloud_governance-1.1.371.dist-info/METADATA,sha256=hUdANpPDEvFa8C4hYH4DQCeFBg2pyN7wi0FP8Hv4yWc,11384
269
+ cloud_governance-1.1.371.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
270
+ cloud_governance-1.1.371.dist-info/top_level.txt,sha256=jfB1fgj7jvx3YZkZA4G6hFeS1RHO7J7XtnbjuMNMRww,17
271
+ cloud_governance-1.1.371.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5