Skip to content

Commit

Permalink
feat!: parameterise snapshot admin username (#2)
Browse files Browse the repository at this point in the history
# 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: NASA-IMPACT/csda-project#753 (comment)

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 🎉

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: NASA-IMPACT/csda-project#753

---------

Co-authored-by: Chris Holden <[email protected]>
  • Loading branch information
ciaransweet and ceholden authored Dec 2, 2024
1 parent 540c249 commit 9ffd759
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 14 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length=88
81 changes: 76 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ 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_,
)

from constructs import Construct

from cdk_bootstrapped_db.helpers import create_database_server
from cdk_bootstrapped_db.constructs import BootstrappedDb


Expand All @@ -24,7 +22,6 @@ class MyDatabase(Construct):
self,
scope: Construct,
id: str,
name: str,
secrets_prefix: str,
db_server: rds.DatabaseInstance,
vpc: ec2.IVpc,
Expand Down Expand Up @@ -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_,
Expand All @@ -83,7 +79,6 @@ class MyDatabase(Construct):
self,
scope: Construct,
id: str,
name: str,
secrets_prefix: str,
vpc: ec2.IVpc,
**kwargs,
Expand Down Expand Up @@ -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
```
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.0
3.0.0
17 changes: 10 additions & 7 deletions cdk_bootstrapped_db/constructs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
import os
from datetime import datetime
from typing import Optional, Union

from aws_cdk import (
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion cdk_bootstrapped_db/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
),
)

Expand Down

0 comments on commit 9ffd759

Please sign in to comment.