cloud-governance 1.1.351__py3-none-any.whl → 1.1.353__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.
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import os
2
3
  import smtplib
3
4
  from email.message import EmailMessage
4
5
  from email.mime.multipart import MIMEMultipart
@@ -12,6 +13,7 @@ from cloud_governance.common.logger.init_logger import logger
12
13
 
13
14
  # https://github.com/redhat-performance/quads/blob/master/quads/tools/postman.py
14
15
  from cloud_governance.common.logger.logger_time_stamp import logger_time_stamp
16
+ from cloud_governance.common.utils.api_requests import APIRequests
15
17
  from cloud_governance.main.environment_variables import environment_variables
16
18
 
17
19
 
@@ -45,7 +47,9 @@ class Postfix:
45
47
  self.__POSTFIX_HOST = environment_variables.POSTFIX_HOST
46
48
  self.__POSTFIX_PORT = environment_variables.POSTFIX_PORT
47
49
  self.bucket_name, self.key = self.get_bucket_name()
50
+ self.__perf_services_url = os.environ.get('PERF_SERVICES_URL')
48
51
  self.__es_index = 'cloud-governance-mail-messages'
52
+ self.__api_request = APIRequests()
49
53
  if self.__es_host:
50
54
  self.__es_operations = ElasticSearchOperations(es_host=self.__es_host, es_port=self.__es_port)
51
55
  if self.__policy_output:
@@ -74,7 +78,7 @@ class Postfix:
74
78
 
75
79
  def prettify_to(self, to: Union[str, list]):
76
80
  """
77
- This method prettify to
81
+ This method prettifies to
78
82
  :param to:
79
83
  :type to:
80
84
  :return:
@@ -89,7 +93,7 @@ class Postfix:
89
93
 
90
94
  def prettify_cc(self, cc: Union[str, list], to: str = ''):
91
95
  """
92
- This method prettify cc
96
+ This method prettifies cc
93
97
  :param to:
94
98
  :type to:
95
99
  :param cc:
@@ -103,6 +107,48 @@ class Postfix:
103
107
  to = self.prettify_to(to=to).split(',')
104
108
  return ','.join(list(set(cc_unique_values) - set(to)))
105
109
 
110
+ def __save_to_elastic(self, **kwargs):
111
+ """
112
+ This method uploads data to elasticsearch
113
+ :param kwargs:
114
+ :return:
115
+ """
116
+ es_data = kwargs.get('es_data')
117
+ data = {'Policy': self.__policy,
118
+ 'Account': self.__account.upper(),
119
+ 'MessageType': kwargs.get('message_type', 'alert')}
120
+ if es_data:
121
+ data.update(es_data)
122
+ if kwargs.get('resource_id'):
123
+ data['resource_id'] = kwargs['resource_id']
124
+ if kwargs.get('extra_purse'):
125
+ data['extra_purse'] = round(kwargs['extra_purse'], 3)
126
+ if kwargs.get('remaining_budget'):
127
+ data['remaining_budget'] = kwargs['remaining_budget']
128
+ if self.__es_host:
129
+ self.__es_operations.upload_to_elasticsearch(data=data, index=self.__es_index)
130
+ logger.info(f'Uploaded to es index: {self.__es_index}')
131
+ else:
132
+ logger.info('Error missing the es_host')
133
+
134
+ def __save_to_s3(self, content: str, **kwargs):
135
+ """
136
+ This method save mail message to s3 bucket
137
+ :return:
138
+ """
139
+ if kwargs.get('filename'):
140
+ file_name = kwargs['filename'].split('/')[-1]
141
+ date_key = datetime.datetime.now().strftime("%Y%m%d%H")
142
+ if self.__policy_output:
143
+ self.__s3_operations.upload_file(file_name_path=kwargs['filename'],
144
+ bucket=self.bucket_name,
145
+ key=f'{self.key}/{self.__policy}/{date_key}',
146
+ upload_file=file_name)
147
+ s3_path = f'{self.__policy_output}/logs/{self.__policy}/{date_key}/{file_name}'
148
+ content += f'\n\nresource_file_path: s3://{s3_path}\n\n'
149
+ logger.info("File Saved to S3")
150
+ self.__save_to_elastic(**kwargs)
151
+
106
152
  @logger_time_stamp
107
153
  def send_email_postfix(self, subject: str, to: any, cc: list, content: str, **kwargs):
108
154
  if self.__email_alert:
@@ -112,65 +158,56 @@ class Postfix:
112
158
  cc = self.__mail_cc
113
159
  if not self.__ldap_search.get_user_details(user_name=to):
114
160
  cc.append('athiruma@redhat.com')
115
- msg = MIMEMultipart('alternative')
116
- msg["Subject"] = subject
117
- msg["From"] = "%s <%s>" % (
118
- 'cloud-governance',
119
- "@".join(["noreply-cloud-governance", 'redhat.com']),
120
- )
121
- msg["To"] = self.prettify_to(to)
122
- msg["Cc"] = self.prettify_cc(cc=cc, to=to)
123
- # msg.add_header("Reply-To", self.reply_to)
124
- # msg.add_header("User-Agent", self.reply_to)
125
- if kwargs.get('filename'):
126
- attachment = MIMEText(open(kwargs['filename']).read())
127
- attachment.add_header('Content-Disposition', 'attachment',
128
- filename=kwargs['filename'].split('/')[-1])
129
- msg.attach(attachment)
130
- if kwargs.get('mime_type'):
131
- msg.attach(MIMEText(content, kwargs.get('mime_type')))
161
+ response = {'ok': True}
162
+ to = self.prettify_to(to)
163
+ cc = self.prettify_cc(cc)
164
+ kwargs.setdefault('es_data', {}).update({'To': to, 'Cc': cc, 'Message': content})
165
+ if self.__perf_services_url:
166
+ body = {
167
+ "cc": cc,
168
+ "to": to,
169
+ "subject": subject,
170
+ "mail_body": content,
171
+ }
172
+ if kwargs.get('filename'):
173
+ body['file_content'] = open(kwargs['filename']).read()
174
+ body['filename'] = kwargs['filename'].split('/')[-1]
175
+ api_url = self.__perf_services_url + f"/postfix/send_mail"
176
+ response = self.__api_request.post(api_url, json=body)
177
+ response = response.json()
132
178
  else:
133
- msg.attach(MIMEText(content))
134
- email_string = msg.as_string()
135
- try:
136
- with smtplib.SMTP(self.__POSTFIX_HOST, self.__POSTFIX_PORT) as s:
137
- try:
138
- logger.debug(email_string)
139
- s.send_message(msg)
140
- if isinstance(to, str):
141
- logger.warn(f'Mail sent successfully to {to}@redhat.com')
142
- elif isinstance(to, list):
143
- logger.warn(f'Mail sent successfully to {", ".join(to)}@redhat.com')
144
- if kwargs.get('filename'):
145
- file_name = kwargs['filename'].split('/')[-1]
146
- date_key = datetime.datetime.now().strftime("%Y%m%d%H")
147
- if self.__policy_output:
148
- self.__s3_operations.upload_file(file_name_path=kwargs['filename'],
149
- bucket=self.bucket_name,
150
- key=f'{self.key}/{self.__policy}/{date_key}',
151
- upload_file=file_name)
152
- s3_path = f'{self.__policy_output}/logs/{self.__policy}/{date_key}/{file_name}'
153
- content += f'\n\nresource_file_path: s3://{s3_path}\n\n'
154
- es_data = kwargs.get('es_data')
155
- data = {'Policy': self.__policy, 'To': to, 'Cc': cc, 'Message': content,
156
- 'Account': self.__account.upper(), 'MessageType': kwargs.get('message_type', 'alert')}
157
- if es_data:
158
- data.update(es_data)
159
- if kwargs.get('resource_id'):
160
- data['resource_id'] = kwargs['resource_id']
161
- if kwargs.get('extra_purse'):
162
- data['extra_purse'] = round(kwargs['extra_purse'], 3)
163
- if kwargs.get('remaining_budget'):
164
- data['remaining_budget'] = kwargs['remaining_budget']
165
- if self.__es_host:
166
- self.__es_operations.upload_to_elasticsearch(data=data, index=self.__es_index)
167
- logger.warn(f'Uploaded to es index: {self.__es_index}')
168
- else:
169
- logger.warn('Error missing the es_host')
170
- except smtplib.SMTPException as ex:
171
- logger.error(f'Error while sending mail, {ex}')
172
- return False
179
+ msg = MIMEMultipart('alternative')
180
+ msg["Subject"] = subject
181
+ msg["From"] = "%s <%s>" % (
182
+ 'cloud-governance',
183
+ "@".join(["noreply-cloud-governance", 'redhat.com']),
184
+ )
185
+ msg["To"] = to
186
+ msg["Cc"] = cc
187
+ # msg.add_header("Reply-To", self.reply_to)
188
+ # msg.add_header("User-Agent", self.reply_to)
189
+ if kwargs.get('filename'):
190
+ attachment = MIMEText(open(kwargs['filename']).read())
191
+ attachment.add_header('Content-Disposition', 'attachment',
192
+ filename=kwargs['filename'].split('/')[-1])
193
+ msg.attach(attachment)
194
+ if kwargs.get('mime_type'):
195
+ msg.attach(MIMEText(content, kwargs.get('mime_type')))
196
+ else:
197
+ msg.attach(MIMEText(content))
198
+ try:
199
+ with smtplib.SMTP(self.__POSTFIX_HOST, self.__POSTFIX_PORT) as s:
200
+ try:
201
+ s.send_message(msg)
202
+ except smtplib.SMTPException as ex:
203
+ logger.error(f'Error while sending mail, {ex}')
204
+ response = {
205
+ 'ok': True
206
+ }
207
+ except Exception as err:
208
+ logger.error(f'Some error occurred, {err}')
209
+ if isinstance(response, dict) and response.get('ok'):
210
+ logger.info(f'Mail sent successfully to {to}')
211
+ self.__save_to_s3(content, **kwargs)
173
212
  return True
174
- except Exception as err:
175
- logger.error(f'Some error occurred, {err}')
176
- return False
213
+ return False
@@ -7,6 +7,7 @@ HOURS_IN_SECONDS = 3600
7
7
  HOURS_IN_DAY = 24
8
8
  HOURS_IN_MONTH = 720
9
9
  TOTAL_BYTES_IN_KIB = 1024
10
+ INSTANCE_START_PREFIX = 'i-'
10
11
 
11
12
  DATE_FORMAT = "%Y-%m-%d"
12
13
  DATE_TIME_FORMAT_T = "%Y-%m-%dT%h:%m"
@@ -1,6 +1,7 @@
1
1
  from datetime import datetime, timedelta
2
2
 
3
3
  from cloud_governance.common.logger.init_logger import logger
4
+ from cloud_governance.common.utils.configs import INSTANCE_START_PREFIX
4
5
  from cloud_governance.policy.policy_operations.aws.tag_non_cluster.non_cluster_operations import NonClusterOperations
5
6
 
6
7
 
@@ -36,7 +37,8 @@ class TagNonClusterResources(NonClusterOperations):
36
37
  @param tags:
37
38
  @return:
38
39
  """
39
- username = self.get_username(start_time=launch_time, resource_id=instance_id, resource_type='AWS::EC2::Instance', tags=tags)
40
+ username = self.get_username(start_time=launch_time, resource_id=instance_id,
41
+ resource_type='AWS::EC2::Instance', tags=tags)
40
42
  search_tags = []
41
43
  user_tags = []
42
44
  if not username:
@@ -73,8 +75,10 @@ class TagNonClusterResources(NonClusterOperations):
73
75
  if add_tags:
74
76
  if self.dry_run == 'no':
75
77
  try:
76
- self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags, resource_ids=[instance_id], tags=add_tags)
77
- logger.info(f'Added tags to instance: {instance_id} total: {len(add_tags)} tags: {add_tags}')
78
+ self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags,
79
+ resource_ids=[instance_id], tags=add_tags)
80
+ logger.info(
81
+ f'Added tags to instance: {instance_id} total: {len(add_tags)} tags: {add_tags}')
78
82
  except Exception as err:
79
83
  logger.info(err)
80
84
  instances_ids.append(instance_id)
@@ -94,7 +98,8 @@ class TagNonClusterResources(NonClusterOperations):
94
98
  volume_id = volume.get('VolumeId')
95
99
  tags = volume.get('Tags')
96
100
  if not self.validate_existing_tag(tags=tags):
97
- username = self.get_username(start_time=volume.get('CreateTime'), resource_id=volume_id, resource_type='AWS::EC2::Volume', tags=tags)
101
+ username = self.get_username(start_time=volume.get('CreateTime'), resource_id=volume_id,
102
+ resource_type='AWS::EC2::Volume', tags=tags)
98
103
  search_tags = []
99
104
  if not username:
100
105
  get_tags, username = self._get_tags_fom_attachments(attachments=volume.get('Attachments'))
@@ -120,8 +125,10 @@ class TagNonClusterResources(NonClusterOperations):
120
125
  if volume_tags:
121
126
  if self.dry_run == 'no':
122
127
  try:
123
- self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags, resource_ids=[volume_id], tags=volume_tags)
124
- logger.info(f'added tags to volume_id: {volume_id} total: {len(volume_tags)} tags: {volume_tags}')
128
+ self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags,
129
+ resource_ids=[volume_id], tags=volume_tags)
130
+ logger.info(
131
+ f'added tags to volume_id: {volume_id} total: {len(volume_tags)} tags: {volume_tags}')
125
132
  except Exception as err:
126
133
  logger.info(err)
127
134
  volume_ids.append(volume_id)
@@ -141,24 +148,33 @@ class TagNonClusterResources(NonClusterOperations):
141
148
  snapshot_id = snapshot.get('SnapshotId')
142
149
  tags = snapshot.get('Tags')
143
150
  if not self.validate_existing_tag(tags=tags):
144
- username = self.get_username(start_time=snapshot.get('StartTime'), resource_id=snapshot_id, resource_type='AWS::EC2::Snapshot', tags=tags)
151
+ username = self.get_username(start_time=snapshot.get('StartTime'), resource_id=snapshot_id,
152
+ resource_type='AWS::EC2::Snapshot', tags=tags)
145
153
  if 'vm_import_image' in username:
146
154
  start_time = snapshot.get('StartTime') + timedelta(seconds=5)
147
155
  end_time = start_time + timedelta(minutes=30)
148
- assume_username = self.get_username(start_time=start_time, resource_id=snapshot_id, resource_type='AWS::EC2::Snapshot', tags=tags, end_time=end_time)
156
+ assume_username = self.get_username(start_time=start_time, resource_id=snapshot_id,
157
+ resource_type='AWS::EC2::Snapshot', tags=tags,
158
+ end_time=end_time)
149
159
  if assume_username:
150
160
  username = assume_username
151
161
  search_tags = []
152
162
  if not username:
153
163
  if snapshot.get('Description') and 'Created' in snapshot.get('Description'):
154
- image_tags, username = self._get_tags_from_snapshot_description_images(description=snapshot.get('Description'))
164
+ image_tags, username = self._get_tags_from_snapshot_description_images(
165
+ description=snapshot.get('Description'))
155
166
  if not username:
156
- instance_id = snapshot.get('Description').split(" ")[2].split("(")[1][:-1]
157
- instances = self._get_instances_data(instance_id)
158
- if instances:
159
- for item in instances:
160
- if item.get('InstanceId') == instance_id:
161
- item_tags, username = self._get_tags_from_instance_item(instance_item=item)
167
+ if INSTANCE_START_PREFIX in snapshot.get('Description'):
168
+ try:
169
+ instance_id = snapshot.get('Description').split(" ")[2].split("(")[1][:-1]
170
+ instances = self._get_instances_data(instance_id)
171
+ if instances:
172
+ for item in instances:
173
+ if item.get('InstanceId') == instance_id:
174
+ item_tags, username = self._get_tags_from_instance_item(
175
+ instance_item=item)
176
+ except Exception as err:
177
+ logger.info(err)
162
178
  else:
163
179
  search_tags.extend(self._append_input_tags())
164
180
  if username:
@@ -179,8 +195,10 @@ class TagNonClusterResources(NonClusterOperations):
179
195
  if snapshot_tags:
180
196
  if self.dry_run == 'no':
181
197
  try:
182
- self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags, resource_ids=[snapshot_id], tags=snapshot_tags)
183
- logger.info(f'added tags to snapshots: {snapshot_id} total: {len(snapshot_tags)} tags: {snapshot_tags}')
198
+ self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags,
199
+ resource_ids=[snapshot_id], tags=snapshot_tags)
200
+ logger.info(
201
+ f'added tags to snapshots: {snapshot_id} total: {len(snapshot_tags)} tags: {snapshot_tags}')
184
202
  except Exception as err:
185
203
  logger.info(err)
186
204
  snapshot_ids.append(snapshot_id)
@@ -203,7 +221,8 @@ class TagNonClusterResources(NonClusterOperations):
203
221
  image_name = image.get('Name')
204
222
  start_time = datetime.fromisoformat(image.get('CreationDate')[:-1] + '+00:00')
205
223
  if not self.validate_existing_tag(tags=tags):
206
- username = self.get_username(start_time=start_time, resource_id=image_id, resource_type='AWS::EC2::Ami', tags=tags, resource_name=image_name)
224
+ username = self.get_username(start_time=start_time, resource_id=image_id, resource_type='AWS::EC2::Ami',
225
+ tags=tags, resource_name=image_name)
207
226
  search_tags = []
208
227
  search_tags.extend(self._append_input_tags())
209
228
  if username:
@@ -223,7 +242,8 @@ class TagNonClusterResources(NonClusterOperations):
223
242
  if image_tags:
224
243
  if self.dry_run == 'no':
225
244
  try:
226
- self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags, resource_ids=[image_id], tags=image_tags)
245
+ self.utils.tag_aws_resources(client_method=self.ec2_client.create_tags,
246
+ resource_ids=[image_id], tags=image_tags)
227
247
  logger.info(f'added tags to image: {image_id} total: {len(image_tags)} tags: {image_tags}')
228
248
  except Exception as err:
229
249
  logger.info(err)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloud-governance
3
- Version: 1.1.351
3
+ Version: 1.1.353
4
4
  Summary: Cloud Governance Tool
5
5
  Home-page: https://github.com/redhat-performance/cloud-governance
6
6
  Author: Red Hat
@@ -128,7 +128,7 @@ cloud_governance/common/logger/logger_time_stamp.py,sha256=sTFdAN3JckYtJDa7kjUWj
128
128
  cloud_governance/common/mails/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
129
  cloud_governance/common/mails/gmail.py,sha256=J2IKlFTRRNDwj1gfRuJU5Yg1AHiex4GHUAjLg3fZ7r0,1858
130
130
  cloud_governance/common/mails/mail_message.py,sha256=YZ0rdwNuAimDzoT1dmeAfLMiXxIVvpm2Fnm1VkHaQ5w,22994
131
- cloud_governance/common/mails/postfix.py,sha256=piiJEnXP2Lwy88GwVX4Hwck_1_wDJix28-crYgbjQ-s,8410
131
+ cloud_governance/common/mails/postfix.py,sha256=SYrXQnDGqY9JffLW8jh2UiGTul9Cc56ZJ0DYzEFuW-k,9304
132
132
  cloud_governance/common/mails/templates/cro_monitor_budget_remain_alert.j2,sha256=RZKL0TCUvu46WpgJpw18zbcjnsNRQkCq9-9r7yXtGE0,928
133
133
  cloud_governance/common/mails/templates/cro_monitor_budget_remain_high_alert.j2,sha256=xXbrenZ6LtB4nlGJDMgwQc5uLtSk2uJ7DEqVyxD1QAg,928
134
134
  cloud_governance/common/mails/templates/cro_request_for_manager_approval.j2,sha256=aLD34Lni5OQnSBwy-v8lZLHUPG796cOFgqnT4dX7UE8,1216
@@ -139,7 +139,7 @@ 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=AinYkks4_Vkxyic8L8mk3tNg1m93proF5xZC5IJK8bc,1165
142
+ cloud_governance/common/utils/configs.py,sha256=8ry4UQT9s_GUivhrCdwS0vOWYN6OJIHDFzbVpi3BYpY,1194
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
@@ -220,7 +220,7 @@ cloud_governance/policy/policy_operations/aws/tag_non_cluster/__init__.py,sha256
220
220
  cloud_governance/policy/policy_operations/aws/tag_non_cluster/non_cluster_operations.py,sha256=PUWS0SE7T56BejBa3FRUK-txq70gVHztNAj1JmB-Ra0,9497
221
221
  cloud_governance/policy/policy_operations/aws/tag_non_cluster/remove_non_cluster_tags.py,sha256=d-RKpWSaZi2ALQaA9_MwqxHwzbSpsdyjQTBacJVoOLw,8362
222
222
  cloud_governance/policy/policy_operations/aws/tag_non_cluster/run_tag_non_cluster_resources.py,sha256=KG0B0sjaI8SI7ZUf13Aw6kOgUfX7u83yazj9lsraPJU,2612
223
- cloud_governance/policy/policy_operations/aws/tag_non_cluster/tag_non_cluster_resources.py,sha256=Fvcu5uB3pvQEol8fLUbYWEQnrz_l3KuDdxl0KM8f0EI,12941
223
+ cloud_governance/policy/policy_operations/aws/tag_non_cluster/tag_non_cluster_resources.py,sha256=yevICSIFuzinE8jltZOX2DLDxBL2ZH7Eg82nQQuQeuI,13994
224
224
  cloud_governance/policy/policy_operations/aws/tag_non_cluster/update_na_tag_resources.py,sha256=HkDNAKqRXmdNTInnww36kyxEg_PHpf0-2G5TmUG3ZNE,6139
225
225
  cloud_governance/policy/policy_operations/aws/tag_user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
226
226
  cloud_governance/policy/policy_operations/aws/tag_user/iam_user_tags.py,sha256=s9lmZPQ2lUrbg0AIvQvoTSQMZhFjLduMZO3befZ7u6Y,3389
@@ -263,8 +263,8 @@ cloud_governance/policy/policy_runners/elasticsearch/__init__.py,sha256=47DEQpj8
263
263
  cloud_governance/policy/policy_runners/elasticsearch/upload_elastic_search.py,sha256=pOwUJWXjJbyTy8iv3Ap8xJGnqQe-5lZgoR8-vGfAVos,1881
264
264
  cloud_governance/policy/policy_runners/ibm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
265
265
  cloud_governance/policy/policy_runners/ibm/policy_runner.py,sha256=V0E_f7F3hXit0aSq4BlfX1Jd4vjR2NEvOWsJ5upvZ4o,1302
266
- cloud_governance-1.1.351.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
267
- cloud_governance-1.1.351.dist-info/METADATA,sha256=XIDGqG496xIcJlP-7UvcYgbxppoCr6Bo-z65FjiUemE,11342
268
- cloud_governance-1.1.351.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
269
- cloud_governance-1.1.351.dist-info/top_level.txt,sha256=jfB1fgj7jvx3YZkZA4G6hFeS1RHO7J7XtnbjuMNMRww,17
270
- cloud_governance-1.1.351.dist-info/RECORD,,
266
+ cloud_governance-1.1.353.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
267
+ cloud_governance-1.1.353.dist-info/METADATA,sha256=m67JWKZBvw6oGdd5E3f4Ya3txAvEXnYNATZVy1xVS2w,11342
268
+ cloud_governance-1.1.353.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
269
+ cloud_governance-1.1.353.dist-info/top_level.txt,sha256=jfB1fgj7jvx3YZkZA4G6hFeS1RHO7J7XtnbjuMNMRww,17
270
+ cloud_governance-1.1.353.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5