Skip to content

Commit 09f5892

Browse files
author
Jim Robinson
committed
Merge branch 'main' of github.com:CroudTech/croudtech-python-bootstrap-app into main
2 parents 1de6e41 + 7186263 commit 09f5892

File tree

1 file changed

+118
-1
lines changed

1 file changed

+118
-1
lines changed

croudtech_bootstrap_app/bootstrap.py

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import sys
4+
35
from typing import TYPE_CHECKING, Any
46

57
import botocore.exceptions
@@ -194,6 +196,10 @@ def __init__(self, name, path, environment: BootstrapEnvironment):
194196
def s3_client(self) -> S3Client:
195197
return self.environment.manager.s3_client
196198

199+
@property
200+
def ssm_client(self) -> SSMClient:
201+
return self.environment.manager.ssm_client
202+
197203
@property
198204
def secrets_client(self) -> SecretsManagerClient:
199205
return self.environment.manager.secrets_client
@@ -243,6 +249,24 @@ def parse_value(self, value):
243249
parsed_value = value
244250
return str(parsed_value).strip()
245251

252+
def cleanup_ssm_parameters(self):
253+
local_value_keys = set(self.convert_flatten(self.local_values).keys() or [])
254+
self.raw = True
255+
remote_value_keys = set(self.remote_values or [])
256+
self.raw = None
257+
258+
orphaned_ssm_parameters = remote_value_keys - local_value_keys
259+
260+
for parameter in orphaned_ssm_parameters:
261+
parameter_id = self.get_parameter_id(parameter)
262+
try:
263+
self.ssm_client.delete_parameter(
264+
Name=self.get_parameter_id(parameter)
265+
)
266+
logger.info(f"Deleted orphaned ssm parameter {parameter}")
267+
except Exception:
268+
logger.info(f"Parameter: {parameter_id} could not be deleted")
269+
246270
def cleanup_secrets(self):
247271
local_secret_keys = self.convert_flatten(self.local_secrets).keys()
248272
remote_secret_keys = self.remote_secret_records.keys()
@@ -296,6 +320,13 @@ def remote_secret_records(self) -> typing.Dict[str, Any]:
296320

297321
return self._remote_secrets
298322

323+
@property
324+
def remote_ssm_parameters(self) -> typing.Dict[str, Any]:
325+
if not hasattr(self, "_remote_parameters"):
326+
self._remote_parameters = self.get_remote_ssm_parameters()
327+
328+
return self._remote_parameters
329+
299330
@property
300331
def remote_values(self) -> typing.Dict[str, Any]:
301332
if not hasattr(self, "_remote_values"):
@@ -325,12 +356,33 @@ def get_remote_params(self, flatten=True):
325356
app_secrets = self.remote_secrets
326357
return {**app_values, **app_secrets}
327358

359+
def get_flattened_parameters(self) -> typing.Dict[str, Any]:
360+
return self.convert_flatten(self.local_values)
361+
328362
def get_flattened_secrets(self) -> typing.Dict[str, Any]:
329363
return self.convert_flatten(self.local_secrets)
364+
365+
def get_parameter_id(self, parameter):
366+
return f"/{self.get_secret_id(parameter)}"
330367

331368
def get_secret_id(self, secret):
332369
return os.path.join("", self.environment.name, self.name, secret)
333370

371+
def put_parameter(self, parameter_id, parameter_value, tags=None, type="String", overwrite=True):
372+
print(f"Creating Parameter {parameter_id}")
373+
self.ssm_client.put_parameter(
374+
Name=parameter_id,
375+
Value=parameter_value,
376+
Type=type,
377+
Overwrite=overwrite,
378+
)
379+
if tags:
380+
self.ssm_client.add_tags_to_resource(
381+
ResourceType="Parameter",
382+
ResourceId=parameter_id,
383+
Tags=tags
384+
)
385+
334386
def create_secret(self, Name, SecretString, Tags, ForceOverwriteReplicaSecret):
335387
print(f"Creating Secret {Name}")
336388
try:
@@ -368,6 +420,31 @@ def backoff_with_custom_exception(self, func, exception, message_prefix="", max_
368420
print(f"Retrying in {delay} seconds...")
369421
time.sleep(delay)
370422

423+
def push_parameters(self):
424+
for parameter, value in self.get_flattened_parameters().items():
425+
parameter_value = str(value)
426+
if (value_size := sys.getsizeof(parameter_value)) > 4096 or not parameter_value:
427+
self.environment.manager.click.secho(
428+
f"Parameter: {parameter} value is too large to store ({value_size})"
429+
)
430+
continue
431+
parameter_id = self.get_parameter_id(parameter)
432+
self.backoff_with_custom_exception(
433+
self.put_parameter,
434+
exception=botocore.exceptions.ClientError,
435+
message_prefix=f"Creating/Updating parameter {parameter_id}",
436+
max_attempts=5,
437+
base_delay=1,
438+
max_delay=10,
439+
factor=2,
440+
parameter_id=parameter_id,
441+
parameter_value=parameter_value,
442+
tags=[
443+
{"Key": "Environment", "Value": self.environment.name},
444+
{"Key": "App", "Value": self.name},
445+
],
446+
)
447+
371448
def push_secrets(self):
372449
for secret, value in self.get_flattened_secrets().items():
373450
sec_val = str(value)
@@ -406,6 +483,16 @@ def fetch_secret_value(self, secret):
406483
return ""
407484
return response["SecretString"]
408485

486+
@property
487+
def remote_ssm_parameter_filters(self):
488+
return [
489+
{
490+
"Key": "Name",
491+
"Option": "Contains",
492+
"Values": [f"/{self.environment.name}/{self.name}"]
493+
}
494+
]
495+
409496
@property
410497
def remote_secret_filters(self):
411498
return [
@@ -415,6 +502,20 @@ def remote_secret_filters(self):
415502
{"Key": "tag-value", "Values": [self.name]},
416503
]
417504

505+
def get_remote_ssm_parameters(self):
506+
paginator = self.ssm_client.get_paginator('describe_parameters')
507+
parameters = {}
508+
filters = self.remote_ssm_parameter_filters
509+
response = paginator.paginate(
510+
ParameterFilters=filters,
511+
)
512+
for page in response:
513+
for parameter in page["Parameters"]:
514+
parameter_key = os.path.split(parameter["Name"])[-1]
515+
# parameters.append(parameter_key)
516+
parameters[parameter_key] = parameter
517+
return parameters
518+
418519
def get_remote_secrets(self) -> typing.Dict[str, str]:
419520
paginator = self.secrets_client.get_paginator("list_secrets")
420521
secrets = {}
@@ -521,6 +622,16 @@ def s3_client(self) -> S3Client:
521622
"s3", region_name=self.region, endpoint_url=self.endpoint_url
522623
)
523624
return self._s3_client
625+
626+
@property
627+
def ssm_client(self):
628+
if not hasattr(self, '_ssm_client'):
629+
self._ssm_client = boto3.client(
630+
"ssm",
631+
region_name=self.region,
632+
endpoint_url=self.endpoint_url
633+
)
634+
return self._ssm_client
524635

525636
@property
526637
def secrets_client(self) -> SecretsManagerClient:
@@ -565,14 +676,20 @@ def initBootstrap(self):
565676
self.click.secho(f"S3 Client Error {err}", bg="red", fg="white")
566677

567678
def put_config(self, delete_first):
568-
679+
self.cleanup_ssm_parameters()
569680
self.cleanup_secrets()
570681
for _environment_name, environment in self.environments.items():
571682
for _app_name, app in environment.apps.items():
572683
# pass
573684
app.upload_to_s3()
685+
app.push_parameters()
574686
app.push_secrets()
575687

688+
def cleanup_ssm_parameters(self):
689+
for _environment_name, environment in self.environments.items():
690+
for _app_name, app in environment.apps.items():
691+
app.cleanup_ssm_parameters()
692+
576693
def cleanup_secrets(self):
577694
for _environment_name, environment in self.environments.items():
578695
for _app_name, app in environment.apps.items():

0 commit comments

Comments
 (0)