Skip to content

Commit 58f7b20

Browse files
author
Jim Robinson
committed
Add backoff to secret creation. Fix orphaned secret key comparison
1 parent 29ae7e5 commit 58f7b20

File tree

1 file changed

+69
-18
lines changed

1 file changed

+69
-18
lines changed

croudtech_bootstrap_app/bootstrap.py

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import TYPE_CHECKING, Any
44

5+
import botocore.exceptions
6+
57
if TYPE_CHECKING:
68
from mypy_boto3_s3 import S3Client
79
from mypy_boto3_secretsmanager import SecretsManagerClient
@@ -17,8 +19,11 @@
1719
from collections.abc import MutableMapping
1820

1921
import boto3
22+
import botocore
2023
import click
2124
import yaml
25+
import time
26+
import re
2227

2328
from croudtech_bootstrap_app.logging import init as initLogs
2429

@@ -27,6 +32,9 @@
2732
logger = initLogs()
2833

2934

35+
AWS_ENDPOINT_URL = os.getenv("AWS_ENDPOINT_URL", None)
36+
37+
3038
class 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

Comments
 (0)