22
33from typing import TYPE_CHECKING , Any
44
5+ import botocore .exceptions
6+
57if TYPE_CHECKING :
68 from mypy_boto3_s3 import S3Client
79 from mypy_boto3_secretsmanager import SecretsManagerClient
1719from collections .abc import MutableMapping
1820
1921import boto3
22+ import botocore
2023import click
2124import yaml
25+ import time
26+ import re
2227
2328from croudtech_bootstrap_app .logging import init as initLogs
2429
2732logger = initLogs ()
2833
2934
35+ AWS_ENDPOINT_URL = os .getenv ("AWS_ENDPOINT_URL" , None )
36+
37+
3038class Utils :
3139 @staticmethod
3240 def chunk_list (data , chunk_size ):
@@ -45,7 +53,7 @@ def __init__(
4553 region = "eu-west-2" ,
4654 include_common = True ,
4755 use_sns = True ,
48- endpoint_url = os . getenv ( " AWS_ENDPOINT_URL" , None ) ,
56+ endpoint_url = AWS_ENDPOINT_URL ,
4957 parse_redis = True ,
5058 ):
5159 self .environment_name = environment_name
@@ -232,11 +240,13 @@ def parse_value(self, value):
232240 return str (parsed_value ).strip ()
233241
234242 def cleanup_secrets (self ):
235- local_secret_keys = self .local_secrets .keys ()
243+ local_secret_keys = self .convert_flatten ( self . local_secrets ) .keys ()
236244 remote_secret_keys = self .remote_secret_records .keys ()
245+
237246 orphaned_secrets = [
238- item for item in remote_secret_keys if item not in local_secret_keys
247+ item for item in remote_secret_keys if re . sub ( r"(-[a-zA-Z]{6})$" , "" , item ) not in local_secret_keys
239248 ]
249+
240250 for secret in orphaned_secrets :
241251 secret_record = self .remote_secrets [secret ]
242252 self .secrets_client .delete_secret (
@@ -311,31 +321,72 @@ def get_flattened_secrets(self) -> typing.Dict[str, Any]:
311321 def get_secret_id (self , secret ):
312322 return os .path .join ("" , self .environment .name , self .name , secret )
313323
324+ def create_secret (self , Name , SecretString , Tags , ForceOverwriteReplicaSecret ):
325+ print (f"Creating Secret { Name } " )
326+ try :
327+ self .secrets_client .create_secret (
328+ Name = Name ,
329+ SecretString = SecretString ,
330+ Tags = [
331+ {"Key" : "Environment" , "Value" : self .environment .name },
332+ {"Key" : "App" , "Value" : self .name },
333+ ],
334+ ForceOverwriteReplicaSecret = True ,
335+ )
336+ except self .secrets_client .exceptions .ResourceExistsException :
337+ self .secrets_client .update_secret (
338+ SecretId = Name ,
339+ SecretString = SecretString ,
340+ )
341+
342+ def backoff_with_custom_exception (self , func , exception , message_prefix = "" , max_attempts = 5 , base_delay = 1 , max_delay = 10 , factor = 2 , * args , ** kwargs ):
343+ attempts = 0
344+ delay = base_delay
345+
346+ while attempts < max_attempts :
347+ try :
348+ result = func (* args , ** kwargs )
349+ return result # Return result if successful
350+ except exception as e :
351+ print (f"{ message_prefix } Attempt { attempts + 1 } failed: { e } " )
352+ attempts += 1
353+ if attempts == max_attempts :
354+ raise # If all attempts fail, raise the last exception
355+
356+ # Backoff logic
357+ delay = min (delay * factor , max_delay )
358+ print (f"Retrying in { delay } seconds..." )
359+ time .sleep (delay )
360+
314361 def push_secrets (self ):
315362 for secret , value in self .get_flattened_secrets ().items ():
316363 sec_val = str (value )
317364 if len (sec_val ) == 0 :
318365 sec_val = "__EMPTY__"
366+ secret_id = self .get_secret_id (secret )
319367 try :
320- self .secrets_client .create_secret (
321- Name = self .get_secret_id (secret ),
368+ self .backoff_with_custom_exception (
369+ self .create_secret ,
370+ exception = botocore .exceptions .ClientError ,
371+ message_prefix = f"Creating/Updating secret { secret_id } " ,
372+ max_attempts = 5 ,
373+ base_delay = 1 ,
374+ max_delay = 10 ,
375+ factor = 2 ,
376+ Name = secret_id ,
322377 SecretString = sec_val ,
323378 Tags = [
324379 {"Key" : "Environment" , "Value" : self .environment .name },
325380 {"Key" : "App" , "Value" : self .name },
326381 ],
327382 ForceOverwriteReplicaSecret = True ,
328383 )
329- except self .secrets_client .exceptions .ResourceExistsException :
330- self .secrets_client .update_secret (
331- SecretId = self .get_secret_id (secret ),
332- SecretString = sec_val ,
333- )
384+
334385 except Exception as err :
335- logger .error (f"Failed to push secret { secret } " )
386+ logger .error (f"Failed to push secret { secret_id } " )
336387 raise err
337388 self .environment .manager .click .secho (
338- f"Pushed { self . environment . name } / { self . name } { secret } "
389+ f"Pushed { secret_id } "
339390 )
340391
341392 def fetch_secret_value (self , secret ):
@@ -430,7 +481,7 @@ def apps(self) -> typing.Dict[str, BootstrapApp]:
430481 return self ._apps
431482
432483 def copy_to_temp (self ):
433- for app_name , app in self .apps .items ():
484+ for _app_name , app in self .apps .items ():
434485 shutil .copy (app .path , self .temp_dir )
435486
436487
@@ -444,7 +495,7 @@ def __init__(
444495 click ,
445496 values_path ,
446497 bucket_name ,
447- endpoint_url = os . getenv ( " AWS_ENDPOINT_URL" , None ) ,
498+ endpoint_url = AWS_ENDPOINT_URL ,
448499 ):
449500 self .prefix = prefix
450501 self .region = region
@@ -506,15 +557,15 @@ def initBootstrap(self):
506557 def put_config (self , delete_first ):
507558
508559 self .cleanup_secrets ()
509- for environment_name , environment in self .environments .items ():
510- for app_name , app in environment .apps .items ():
560+ for _environment_name , environment in self .environments .items ():
561+ for _app_name , app in environment .apps .items ():
511562 # pass
512563 app .upload_to_s3 ()
513564 app .push_secrets ()
514565
515566 def cleanup_secrets (self ):
516- for environment_name , environment in self .environments .items ():
517- for app_name , app in environment .apps .items ():
567+ for _environment_name , environment in self .environments .items ():
568+ for _app_name , app in environment .apps .items ():
518569 app .cleanup_secrets ()
519570
520571 @property
0 commit comments