Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database Resource Provider #683

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions docs/resource-providers/rds-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Relational Database Service Resource Providers

The relational database resource provider tightly couples the lifetime of the RDS resources with the cluster in the
blueprints. It should only be used in production with the centralized/management cluster pattern.

To prevent accidental deletion of data while rebuilding EKS clusters, the default retention policy assigned to the RDS
instances and clusters is `RetainPolicy.SNAPSHOT` which ensures that while the cluster and database is being deleted, it
is first backed-up and then deleted.

In the management cluster pattern, this resource provider is like resources provisioned with ACK or Crossplane. Removal
of the resource from the management cluster by default will drop such resources as well.

### CreateRDSInstanceProvider

Creates an RDS Instance and make it available to the blueprint constructs with the provided name.

This method creates an RDS Instance with the database engine of your choice and in the VPC included in the resource
context or creates a VPC for you.

The `rdsProps` transparently exposes the underlying RDS Instance properties and will accept and pass them upstream to
the RDS instance method creating the database.

Example implementation without providing a custom VPC:

```typescript
const stack = blueprints.EksBlueprint.builder()
.resourceProvider(
GlobalResources.Rds,
new CreateRDSProvider(
{
rdsProps: {
credentials: Credentials.fromGeneratedSecret('admin'),
engine: DatabaseInstanceEngine.mariaDb({
version: MariaDbEngineVersion.VER_10_3
})
},
name: "rds-instance-no-vpc"
}
)
)
.account("123456789")
.region("us-east-1")
.build(app, 'rds-instance-no-vpc');
```

Example implementation while providing a custom VPC:

```typescript
const stack = blueprints.EksBlueprint.builder()
.resourceProvider(
GlobalResources.Vpc,
new VpcProvider(
undefined,
{
primaryCidr: "10.0.0.0/16"
},
)
)
.resourceProvider(
GlobalResources.Rds,
new CreateRDSProvider({
rdsProps: {
credentials: Credentials.fromGeneratedSecret('admin'),
engine: DatabaseInstanceEngine.postgres({
version: PostgresEngineVersion.VER_15_2
})
},
name: 'rds-instance-w-vpc'
})
)
.account("1234567889")
.region("us-east-1")
.build(app, 'rds-instance-w-vpc');

```

### CreateAuroraClusterProvider

Creates an RDS Aurora Cluster and makes it available to the blueprint constructs with the provided name. This method
creates an RDS Cluster with the database engine of your choice and in the VPC included in the resource context or
creates a VPC for you.

The `rdsProps` transparently exposes the underlying RDS Cluster properties and will accept and pass them upstream to the
RDS cluster method creating the database. We recommend using either Aurora Serverless or creating specific reader and
writer instances.

Example implementation without providing a custom VPC:

```typescript
const stack = blueprints.EksBlueprint.builder()
.resourceProvider(
GlobalResources.Rds,
new CreateAuroraClusterProvider({
clusterEngine: DatabaseClusterEngine.auroraPostgres(
{version: AuroraPostgresEngineVersion.VER_14_6}
),
name: "aurora-cluster-no-vpc"
})
)
.account("123456789")
.region("us-east-1")
.build(app, 'aurora-cluster-no-vpc');
```

Example implementation while providing a custom VPC:

```typescript
const stack = blueprints.EksBlueprint.builder()
.resourceProvider(
GlobalResources.Vpc,
new VpcProvider(
undefined,
{
primaryCidr: "10.0.0.0/16",
})
)
.resourceProvider(
GlobalResources.Rds,
new CreateAuroraClusterProvider({
clusterEngine: DatabaseClusterEngine.auroraPostgres(
{version: AuroraPostgresEngineVersion.VER_14_6}
),
name: "aurora-cluster-w-vpc"
})
)
.account("1234567889")
.region("us-east-1")
.build(app, 'aurora-cluster-w-vpc');
```
1 change: 1 addition & 0 deletions lib/resource-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './vpc';
export * from './efs';
export * from './s3';
export * from './amp';
export * from './rds';
111 changes: 111 additions & 0 deletions lib/resource-providers/rds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {IVpc} from "aws-cdk-lib/aws-ec2";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as rds from "aws-cdk-lib/aws-rds";
import {CfnOutput, RemovalPolicy} from "aws-cdk-lib";
import {GlobalResources, ResourceContext, ResourceProvider} from "../spi";

export interface CreateRDSInstanceProviderProps {
readonly name?: string;
readonly rdsProps?: Omit<rds.DatabaseInstanceProps, "vpc"| "vpcSubnets" | "databaseName" | "removalPolicy">;
}

export class CreateRDSProvider implements ResourceProvider<rds.IDatabaseInstance> {
readonly options: CreateRDSInstanceProviderProps;

/**
* Constructs a new RDS Provider.
*
* @param {CreateRDSInstanceProviderProps} options - The options for creating the RDS Provider.
*/

constructor(options: CreateRDSInstanceProviderProps) {
this.options = options;
}

provide(context: ResourceContext): rds.IDatabaseInstance {
const id = context.scope.node.id;

const rdsVpc = context.get(GlobalResources.Vpc) as IVpc ?? new ec2.Vpc(
context.scope,
`${this.options.name}-${id}-Vpc`
);

const instanceProps: rds.DatabaseInstanceProps = {
...this.options.rdsProps,
vpc: rdsVpc,
removalPolicy: RemovalPolicy.SNAPSHOT,
deletionProtection: true,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
}
} as rds.DatabaseInstanceProps;

let rdsInstance: rds.DatabaseInstance = new rds.DatabaseInstance(
context.scope,
this.options.name || `${id}-RDSInstance`,
instanceProps
);

new CfnOutput(context.scope, "RDSInstanceId", {
value: rdsInstance.instanceIdentifier
});

new CfnOutput(context.scope, "RDSSecretIdentifier", {
value: rdsInstance.secret!.secretArn
});

return rdsInstance;
}
}

export interface CreateAuroraClusterProviderProps {
readonly name?: string;
readonly clusterEngine: rds.IClusterEngine;
readonly clusterProps?: Omit<rds.DatabaseClusterProps, "engine" | "vpc" | "vpcSubnets">
}

export class CreateAuroraClusterProvider implements ResourceProvider<rds.IDatabaseCluster> {
readonly options: CreateAuroraClusterProviderProps;

/**
* Constructor for the CreateAuroraClusterProvider class.
*
* @param {CreateAuroraClusterProviderProps} options - The options for creating an Aurora cluster provider.
*/
constructor(options: CreateAuroraClusterProviderProps) {
this.options = options;
}

provide(context: ResourceContext): rds.IDatabaseCluster {
const id = context.scope.node.id;

const auroraVpc = context.get(GlobalResources.Vpc) as IVpc ?? new ec2.Vpc(
context.scope,
`${this.options.name}-${id}-Vpc`
);

const clusterProps: rds.DatabaseClusterProps = {
...this.options.clusterProps,
removalPolicy: RemovalPolicy.SNAPSHOT,
deletionProtection: true,
vpc: auroraVpc,
} as rds.DatabaseClusterProps;

let auroraInstance = new rds.DatabaseCluster(
context.scope,
this.options.name || `${id}-AuroraCluster`,
clusterProps
);

new CfnOutput(context.scope, "AuroraClusterId", {
value: auroraInstance.clusterIdentifier
});

new CfnOutput(context.scope, "AuroraSecretIdentifier", {
value: auroraInstance.secret!.secretArn
});

return auroraInstance;
}

}
3 changes: 2 additions & 1 deletion lib/spi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface BlockDeviceMapping {
ebs?: EbsVolumeMapping;
noDevice?: string;
}

export interface EbsVolumeMapping {
deleteOnTermination?: boolean;
iops?: number;
Expand Down Expand Up @@ -154,6 +154,7 @@ export enum GlobalResources {
Certificate = 'certificate',
KmsKey = 'kms-key',
Amp = 'amp',
Rds = 'rds'
}

/**
Expand Down
Loading
Loading