From 9ffd759ae01f798ace79214ac1fdb3f40e9d5f11 Mon Sep 17 00:00:00 2001 From: Ciaran Sweet <9111975+ciaransweet@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:51:27 +0000 Subject: [PATCH] feat!: parameterise snapshot admin username (#2) # What this PR is This PR parameterises the `db_snapshot_admin_username` parameter. This is because the `create_database_server` function internally was hardcoding the admin username of snapshots, which we discovered didn't align to our example in CSDA STAC API: https://github.com/NASA-IMPACT/csda-project/issues/753#issuecomment-2504708053 With this change, we can now override this value so that when a DB is restored from a snapshot, its admin user just gets a new password generated and we don't lock ourselves out :tada: As this is a breaking change (changed the default value to be the RDS default value _and_ parameterised it) I've bumped the version - Not sure how we release this, but looks like we just install via git url in the projects that use this. # What I changed * Bumped version in `VERSION` * Added in `.flake8` just to reduce some linting issues (I envision in another workstream we actally give this repo some TLC in terms of dev tooling) * Added a new example to the `README.md` for the above changes and removed some unused vars in the other examples * Lint fixes in `cdk_bootstrapped_db/constructs.py` * Parameterised `db_snapshot_admin_username` in `cdk_boostrapped_db/helpers.py:create_database_server` # How you can test this If you're using the `create_database_server` functionality with a snapshot, you should be able to install this new version and just provide `db_snapshot_admin_username` as the value of the admin user of the snapshot and you should get a cdk diff with no changes to that construct. **Or** just watch this issue as we tackle it: https://github.com/NASA-IMPACT/csda-project/issues/753 --------- Co-authored-by: Chris Holden --- .flake8 | 2 + README.md | 81 +++++++++++++++++++++++++++++-- VERSION | 2 +- cdk_bootstrapped_db/constructs.py | 17 ++++--- cdk_bootstrapped_db/helpers.py | 5 +- 5 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e14b761 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length=88 diff --git a/README.md b/README.md index a186610..bbbdd20 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ This CDK Construct enables provisioning an RDS Database (and optionally a DB Ser ```python import os from aws_cdk import ( - Duration, aws_ec2 as ec2, aws_rds as rds, aws_lambda as lambda_, @@ -15,7 +14,6 @@ from aws_cdk import ( from constructs import Construct -from cdk_bootstrapped_db.helpers import create_database_server from cdk_bootstrapped_db.constructs import BootstrappedDb @@ -24,7 +22,6 @@ class MyDatabase(Construct): self, scope: Construct, id: str, - name: str, secrets_prefix: str, db_server: rds.DatabaseInstance, vpc: ec2.IVpc, @@ -66,7 +63,6 @@ This package comes with a helper function for setting up a new RDS server. The a ```python import os from aws_cdk import ( - Duration, aws_ec2 as ec2, aws_rds as rds, aws_lambda as lambda_, @@ -83,7 +79,6 @@ class MyDatabase(Construct): self, scope: Construct, id: str, - name: str, secrets_prefix: str, vpc: ec2.IVpc, **kwargs, @@ -126,3 +121,79 @@ class MyDatabase(Construct): self.db_connection_secret = self.db.secret ``` + +### Creating an RDS Server from a Snapshot + +If you have a snapshot of an RDS Server that you wish to restore in a new deployment, you can pass in the optional parameter +`db_snapshot_arn` to `create_database_server` instead of using `db_name`. + +This snapshot must reside in the region you're deploying into, you can copy a snapshot across regions following these docs: +https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_CopySnapshot.html + +By default, the created server will use the admin username of `postgres` - If the RDS Server your snapshot is from used a +different admin username, you can provide it with the parameter `db_snapshot_admin_username`. + +This whole scenario is shown below: + +```python +import os +from aws_cdk import ( + aws_ec2 as ec2, + aws_rds as rds, + aws_lambda as lambda_, +) + +from constructs import Construct + +from cdk_bootstrapped_db.helpers import create_database_server +from cdk_bootstrapped_db.constructs import BootstrappedDb + + +class MyDatabase(Construct): + def __init__( + self, + scope: Construct, + id: str, + secrets_prefix: str, + vpc: ec2.IVpc, + **kwargs, + ): + super().__init__(scope, id, **kwargs) + + self.db_server = create_database_server( + self, + "MyDBServer", + identifier="mydbserver", + vpc=vpc, + db_snapshot_arn="arn:aws:rds:us-west-2:123456789012:snapshot:mysql-instance1-snapshot-20130805", + db_snapshot_admin_username="myadminusername", + db_version=rds.PostgresEngineVersion.VER_14, + instance_type=ec2.InstanceType.of( + ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.NANO + ), + ) + + db_setup_handler = lambda_.Function( + self, + "RunMigrations", + handler="handler.handler", + runtime=lambda_.Runtime.PYTHON_3_8, + code=lambda_.Code.from_docker_build( + path=os.path.abspath("."), + file="Dockerfile", + ), + vpc=vpc, + ) + + self.db = BootstrappedDb( + self, + "MyDB", + db=self.db_server, + new_dbname="mydb", + new_username="mydbuser", + secrets_prefix=secrets_prefix, + handler=db_setup_handler, + ) + + self.db_connection_secret = self.db.secret +``` diff --git a/VERSION b/VERSION index 50aea0e..4a36342 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0 \ No newline at end of file +3.0.0 diff --git a/cdk_bootstrapped_db/constructs.py b/cdk_bootstrapped_db/constructs.py index 2733d67..86c4c75 100644 --- a/cdk_bootstrapped_db/constructs.py +++ b/cdk_bootstrapped_db/constructs.py @@ -1,6 +1,5 @@ import json import os -from datetime import datetime from typing import Optional, Union from aws_cdk import ( @@ -56,7 +55,9 @@ def __init__( secret_string_template=json.dumps( { "dbname": new_dbname, - "engine": db_engine if db_engine else db.engine.engine_type, # type: ignore + "engine": db_engine + if db_engine + else db.engine.engine_type, # type: ignore "port": db.instance_endpoint.port, "host": db.instance_endpoint.hostname, "username": new_username, @@ -83,7 +84,9 @@ def __init__( secret_string_template=json.dumps( { "dbname": new_dbname, - "engine": db_engine if db_engine else db.engine.engine_type, # type: ignore + "engine": db_engine + if db_engine + else db.engine.engine_type, # type: ignore "port": db.instance_endpoint.port, "host": db.instance_endpoint.hostname, "username": read_only_username, @@ -92,7 +95,7 @@ def __init__( generate_string_key="password", exclude_punctuation=True, ), - description=f"Read-only user secret deployed by {Stack.of(self).stack_name}", + description=f"Read-only user secret deployed by {Stack.of(self).stack_name}", # noqa: E501 ) self.provider = custom_resources.Provider( @@ -108,9 +111,9 @@ def __init__( # Optionally include the read-only secret ARN if self.read_only_secret: - resource_properties["read_only_user_secret_arn"] = ( - self.read_only_secret.secret_arn - ) + resource_properties[ + "read_only_user_secret_arn" + ] = self.read_only_secret.secret_arn self.resource = CustomResource( scope=scope, diff --git a/cdk_bootstrapped_db/helpers.py b/cdk_bootstrapped_db/helpers.py index 2548f6a..baf1c9e 100644 --- a/cdk_bootstrapped_db/helpers.py +++ b/cdk_bootstrapped_db/helpers.py @@ -11,6 +11,9 @@ def create_database_server( vpc: ec2.IVpc, subnet_selection: ec2.SubnetSelection, deletion_protect: bool, + # Default admin username for Postgres is "postgres" for rds.DatabaseInstance + # https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_rds/DatabaseInstance.html#aws_cdk.aws_rds.DatabaseInstance + db_snapshot_admin_username: str = "postgres", db_name: Optional[str] = None, db_snapshot_arn: Optional[str] = None, db_version: Optional[rds.PostgresEngineVersion] = None, @@ -51,7 +54,7 @@ def create_database_server( **params, snapshot_identifier=db_snapshot_arn, credentials=rds.SnapshotCredentials.from_generated_password( - username="superuser" + username=db_snapshot_admin_username ), )