Skip to content

Commit 453ef7a

Browse files
Merge pull request #24 from CroudTech/feature/CP-6506
CP-6506 push params to s3 + SSM
2 parents a6b08f6 + 5f615b8 commit 453ef7a

File tree

1 file changed

+113
-1
lines changed

1 file changed

+113
-1
lines changed

croudtech_bootstrap_app/bootstrap.py

Lines changed: 113 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
@@ -239,6 +245,19 @@ def parse_value(self, value):
239245
parsed_value = value
240246
return str(parsed_value).strip()
241247

248+
def cleanup_ssm_parameters(self):
249+
local_value_keys = set(self.convert_flatten(self.local_values).keys() or [])
250+
remote_value_keys = set(self.remote_ssm_parameters.keys() or [])
251+
252+
orphaned_ssm_parameters = remote_value_keys - local_value_keys
253+
254+
for parameter in orphaned_ssm_parameters:
255+
if parameter_record := self.remote_ssm_parameters.get(parameter):
256+
self.ssm_client.delete_parameter(
257+
Name=parameter_record["Name"]
258+
)
259+
logger.info(f"Deleted orphaned ssm parameter {parameter}")
260+
242261
def cleanup_secrets(self):
243262
local_secret_keys = self.convert_flatten(self.local_secrets).keys()
244263
remote_secret_keys = self.remote_secret_records.keys()
@@ -292,6 +311,13 @@ def remote_secret_records(self) -> typing.Dict[str, Any]:
292311

293312
return self._remote_secrets
294313

314+
@property
315+
def remote_ssm_parameters(self) -> typing.Dict[str, Any]:
316+
if not hasattr(self, "_remote_parameters"):
317+
self._remote_parameters = self.get_remote_ssm_parameters()
318+
319+
return self._remote_parameters
320+
295321
@property
296322
def remote_values(self) -> typing.Dict[str, Any]:
297323
if not hasattr(self, "_remote_values"):
@@ -315,12 +341,33 @@ def get_remote_params(self, flatten=True):
315341
app_secrets = self.remote_secrets
316342
return {**app_values, **app_secrets}
317343

344+
def get_flattened_parameters(self) -> typing.Dict[str, Any]:
345+
return self.convert_flatten(self.local_values)
346+
318347
def get_flattened_secrets(self) -> typing.Dict[str, Any]:
319348
return self.convert_flatten(self.local_secrets)
349+
350+
def get_parameter_id(self, parameter):
351+
return f"/{self.get_secret_id(parameter)}"
320352

321353
def get_secret_id(self, secret):
322354
return os.path.join("", self.environment.name, self.name, secret)
323355

356+
def put_parameter(self, parameter_id, parameter_value, tags=None, type="String", overwrite=True):
357+
print(f"Creating Parameter {parameter_id}")
358+
self.ssm_client.put_parameter(
359+
Name=parameter_id,
360+
Value=parameter_value,
361+
Type=type,
362+
Overwrite=overwrite,
363+
)
364+
if tags:
365+
self.ssm_client.add_tags_to_resource(
366+
ResourceType="Parameter",
367+
ResourceId=parameter_id,
368+
Tags=tags
369+
)
370+
324371
def create_secret(self, Name, SecretString, Tags, ForceOverwriteReplicaSecret):
325372
print(f"Creating Secret {Name}")
326373
try:
@@ -358,6 +405,31 @@ def backoff_with_custom_exception(self, func, exception, message_prefix="", max_
358405
print(f"Retrying in {delay} seconds...")
359406
time.sleep(delay)
360407

408+
def push_parameters(self):
409+
for parameter, value in self.get_flattened_parameters().items():
410+
parameter_value = str(value)
411+
if (value_size := sys.getsizeof(parameter_value)) > 4096:
412+
self.environment.manager.click.secho(
413+
f"Parameter: {parameter} value is too large to store ({value_size})"
414+
)
415+
continue
416+
parameter_id = self.get_parameter_id(parameter)
417+
self.backoff_with_custom_exception(
418+
self.put_parameter,
419+
exception=botocore.exceptions.ClientError,
420+
message_prefix=f"Creating/Updating parameter {parameter_id}",
421+
max_attempts=5,
422+
base_delay=1,
423+
max_delay=10,
424+
factor=2,
425+
parameter_id=parameter_id,
426+
parameter_value=parameter_value,
427+
tags=[
428+
{"Key": "Environment", "Value": self.environment.name},
429+
{"Key": "App", "Value": self.name},
430+
],
431+
)
432+
361433
def push_secrets(self):
362434
for secret, value in self.get_flattened_secrets().items():
363435
sec_val = str(value)
@@ -396,6 +468,16 @@ def fetch_secret_value(self, secret):
396468
return ""
397469
return response["SecretString"]
398470

471+
@property
472+
def remote_ssm_parameter_filters(self):
473+
return [
474+
{
475+
"Key": "Name",
476+
"Option": "Contains",
477+
"Values": [f"/{self.environment.name}/{self.name}"]
478+
}
479+
]
480+
399481
@property
400482
def remote_secret_filters(self):
401483
return [
@@ -405,6 +487,20 @@ def remote_secret_filters(self):
405487
{"Key": "tag-value", "Values": [self.name]},
406488
]
407489

490+
def get_remote_ssm_parameters(self):
491+
paginator = self.ssm_client.get_paginator('describe_parameters')
492+
parameters = {}
493+
filters = self.remote_ssm_parameter_filters
494+
response = paginator.paginate(
495+
ParameterFilters=filters,
496+
)
497+
for page in response:
498+
for parameter in page["Parameters"]:
499+
parameter_key = os.path.split(parameter["Name"])[-1]
500+
# parameters.append(parameter_key)
501+
parameters[parameter_key] = parameter
502+
return parameters
503+
408504
def get_remote_secrets(self) -> typing.Dict[str, str]:
409505
paginator = self.secrets_client.get_paginator("list_secrets")
410506
secrets = {}
@@ -511,6 +607,16 @@ def s3_client(self) -> S3Client:
511607
"s3", region_name=self.region, endpoint_url=self.endpoint_url
512608
)
513609
return self._s3_client
610+
611+
@property
612+
def ssm_client(self):
613+
if not hasattr(self, '_ssm_client'):
614+
self._ssm_client = boto3.client(
615+
"ssm",
616+
region_name=self.region,
617+
endpoint_url=self.endpoint_url
618+
)
619+
return self._ssm_client
514620

515621
@property
516622
def secrets_client(self) -> SecretsManagerClient:
@@ -555,14 +661,20 @@ def initBootstrap(self):
555661
self.click.secho(f"S3 Client Error {err}", bg="red", fg="white")
556662

557663
def put_config(self, delete_first):
558-
664+
self.cleanup_ssm_parameters()
559665
self.cleanup_secrets()
560666
for _environment_name, environment in self.environments.items():
561667
for _app_name, app in environment.apps.items():
562668
# pass
563669
app.upload_to_s3()
670+
app.push_parameters()
564671
app.push_secrets()
565672

673+
def cleanup_ssm_parameters(self):
674+
for _environment_name, environment in self.environments.items():
675+
for _app_name, app in environment.apps.items():
676+
app.cleanup_ssm_parameters()
677+
566678
def cleanup_secrets(self):
567679
for _environment_name, environment in self.environments.items():
568680
for _app_name, app in environment.apps.items():

0 commit comments

Comments
 (0)