Skip to content
Open
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
4 changes: 4 additions & 0 deletions integ/aws/storage/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ rds.cluster: ## Test DatabaseCluster L2 (live Aurora PostgreSQL Serverless v2 +
go test -v -count 1 -timeout 60m ./... -run ^TestRdsCluster$
.PHONY: rds.cluster

rds.proxy: ## Test DatabaseProxy L2 (live RDS Proxy fronting MySQL db.t3.micro)
go test -v -count 1 -timeout 45m ./... -run ^TestRdsProxy$
.PHONY: rds.proxy

bucket-notifications: ## Test S3 Bucket with EventBridge Notifications
go test -v -count 1 -timeout 15m ./... -run ^TestBucketNotifications$
.PHONY: bucket-notifications
89 changes: 89 additions & 0 deletions integ/aws/storage/apps/rds.proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Live test for the storage.rds DatabaseProxy L2 (RDS PR 2e): a real RDS
// Proxy fronting a MySQL db.t3.micro deployed through the DatabaseInstance L2,
// authenticating via the instance's generated DatabaseSecret. Exercises the
// CfnDBProxyTargetGroup -> aws_db_proxy_default_target_group + aws_db_proxy_target
// resource split and the proxy role's secret grants.
//
// ServerlessCluster v1 (also in this PR) is deliberately NOT live-tested: AWS
// retired Aurora Serverless v1 (engine_mode "serverless" is no longer
// creatable) -- the L2 ships deprecation-marked for API/migration parity with
// unit-level validation only (see serverless-cluster.ts class docs).
//
// NOTE: the auto-generated DatabaseSecret has a deterministic name and no
// recovery-window override -- a re-run within 30 days of destroy needs
// `aws secretsmanager delete-secret --force-delete-without-recovery` first.
import { App, LocalBackend, TerraformOutput } from "cdktn";
import { aws, Duration } from "../../../../src";

const environmentName = process.env.ENVIRONMENT_NAME ?? "test";
const region = process.env.AWS_REGION ?? "us-east-1";
const outdir = process.env.OUT_DIR ?? "cdktf.out";
const stackName = process.env.STACK_NAME ?? "rds.proxy";

const app = new App({
outdir,
});

const stack = new aws.AwsStack(app, stackName, {
gridUUID: "g22222222-2222",
environmentName,
providerConfig: {
region,
},
});
new LocalBackend(stack, {
path: `${stackName}.tfstate`,
});

const vpc = new aws.compute.Vpc(stack, "Vpc", {
maxAzs: 2,
natGateways: 0,
subnetConfiguration: [
{
name: "isolated",
subnetType: aws.compute.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24,
},
],
});

const instance = new aws.storage.rds.DatabaseInstance(stack, "Database", {
// Major-only version: AWS picks the latest available minor, sidestepping
// the retired-minor-version trap the rds.cluster fixture hit.
engine: aws.storage.rds.DatabaseInstanceEngine.mysql({
version: aws.storage.rds.MysqlEngineVersion.VER_8_0,
}),
instanceType: aws.compute.InstanceType.of(
aws.compute.InstanceClass.BURSTABLE3,
aws.compute.InstanceSize.MICRO,
),
vpc,
vpcSubnets: { subnetType: aws.compute.SubnetType.PRIVATE_ISOLATED },
credentials: aws.storage.rds.Credentials.fromGeneratedSecret("dbadmin"),
allocatedStorage: 20,
backupRetention: Duration.days(0),
multiAz: false,
skipFinalSnapshot: true,
});

const proxy = new aws.storage.rds.DatabaseProxy(stack, "Proxy", {
proxyTarget: aws.storage.rds.ProxyTarget.fromInstance(instance),
secrets: [instance.secret!],
vpc,
vpcSubnets: { subnetType: aws.compute.SubnetType.PRIVATE_ISOLATED },
});

new TerraformOutput(stack, "proxy_name", {
value: proxy.dbProxyName,
staticId: true,
});
new TerraformOutput(stack, "proxy_arn", {
value: proxy.dbProxyArn,
staticId: true,
});
new TerraformOutput(stack, "instance_identifier", {
value: instance.instanceIdentifier,
staticId: true,
});

app.synth();
77 changes: 77 additions & 0 deletions integ/aws/storage/rds_proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package test

import (
"context"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/rds"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
"github.com/stretchr/testify/require"
)

// Run the apps/rds.proxy.ts integration test: a real RDS Proxy fronting a
// MySQL db.t3.micro through the DatabaseProxy L2. Validates proxy read-back
// (engine family, TLS), target registration through the
// default-target-group/target resource split, and the post-apply drift oracle.
func TestRdsProxy(t *testing.T) {
runStorageIntegrationTest(t, "rds.proxy", "us-east-1", validateRdsProxy)
}

func validateRdsProxy(t *testing.T, tfWorkingDir string, awsRegion string) {
terraformOptions := test_structure.LoadTerraformOptions(t, tfWorkingDir)
outputs := terraform.OutputAll(t, terraformOptions)

proxyName := outputs["proxy_name"].(string)
instanceID := outputs["instance_identifier"].(string)

ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion))
require.NoError(t, err)
client := rds.NewFromConfig(cfg)

// --- 1. Proxy read-back. ---
dp, err := client.DescribeDBProxies(ctx, &rds.DescribeDBProxiesInput{
DBProxyName: &proxyName,
})
require.NoError(t, err)
require.Len(t, dp.DBProxies, 1)
p := dp.DBProxies[0]
require.Equal(t, "available", string(p.Status))
require.NotNil(t, p.EngineFamily)
require.Equal(t, "MYSQL", *p.EngineFamily)
require.NotNil(t, p.RequireTLS)
require.True(t, *p.RequireTLS, "requireTLS default must map to require_tls")
t.Logf("rds-proxy: %s available (engine family %s, TLS required)", proxyName, *p.EngineFamily)

// --- 2. The instance is registered as a target through the
// default-target-group + target resource split. Registration is
// asynchronous -- poll briefly. ---
deadline := time.Now().Add(5 * time.Minute)
registered := false
for time.Now().Before(deadline) {
targets, terr := client.DescribeDBProxyTargets(ctx, &rds.DescribeDBProxyTargetsInput{
DBProxyName: &proxyName,
})
require.NoError(t, terr)
for _, tgt := range targets.Targets {
if tgt.RdsResourceId != nil && *tgt.RdsResourceId == instanceID {
registered = true
}
}
if registered {
break
}
time.Sleep(15 * time.Second)
}
require.True(t, registered, "instance %s must be registered as a proxy target", instanceID)
t.Logf("rds-proxy: instance %s registered as proxy target", instanceID)

// --- Drift oracle: re-planning the already-applied stack must show zero
// changes (proves the default-target-group/target split reads back cleanly). ---
planExitCode := terraform.PlanExitCode(t, terraformOptions)
require.Equal(t, terraform.DefaultSuccessExitCode, planExitCode,
"expected `tofu plan -detailed-exitcode` to report no drift after apply (got exit code %d)", planExitCode)
}
14 changes: 5 additions & 9 deletions src/aws/storage/rds/cluster-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import type { IClusterEngine } from "./cluster-engine";
import type { Endpoint } from "./endpoint";
// TODO: omitted — upstream also imports `DatabaseProxy`/`DatabaseProxyOptions` from `./proxy` for
// `IDatabaseCluster.addProxy()` below. `./proxy` is not ported in this repo yet — it lands in a
// later PR (RDS PR 2e), matching the existing barrel deferral in `./index.ts` —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts#L3
import type { DatabaseProxy, DatabaseProxyOptions } from "./proxy";
import { IAwsConstruct } from "../../aws-construct";
import * as ec2 from "../../compute";
import * as secretsmanager from "../../encryption";
Expand Down Expand Up @@ -67,11 +64,10 @@ export interface IDatabaseCluster
*/
readonly clusterArn: string;

// TODO: omitted — upstream also declares `addProxy(id, options): DatabaseProxy` here. `DatabaseProxy`
// (and the `./proxy` module it lives in) is not ported in this repo yet — it lands in a later PR
// (RDS PR 2e), matching the existing barrel deferral in `./index.ts` —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts#L60-L62
// addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy;
/**
* Add a new db proxy to this cluster.
*/
addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy;

/**
* Grant the given identity connection access to the Cluster.
Expand Down
15 changes: 11 additions & 4 deletions src/aws/storage/rds/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import type {
SnapshotCredentials,
} from "./props";
import { Credentials, PerformanceInsightRetention } from "./props";
import type { DatabaseProxyOptions } from "./proxy";
import { DatabaseProxy, ProxyTarget } from "./proxy";
import type { ISubnetGroup } from "./subnet-group";
import { SubnetGroup } from "./subnet-group";
import { validateDatabaseClusterProps } from "./validate-database-insights";
Expand Down Expand Up @@ -746,10 +748,15 @@ export abstract class DatabaseClusterBase
});
}

// TODO: omitted — upstream also declares `addProxy(id, options): DatabaseProxy` here.
// `DatabaseProxy`/`./proxy` is not ported yet (RDS PR 2e) — same deferral as
// `DatabaseInstanceBase.addProxy` in `./instance.ts` —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/aws-cdk-lib/aws-rds/lib/cluster.ts#L677-L685
/**
* Add a new db proxy to this cluster.
*/
public addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy {
return new DatabaseProxy(this, id, {
proxyTarget: ProxyTarget.fromCluster(this),
...options,
});
}

/**
* Renders the secret attachment target specifications.
Expand Down
6 changes: 3 additions & 3 deletions src/aws/storage/rds/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export * from "./database-secret";
export * from "./endpoint";
export * from "./option-group";
export * from "./instance";
// TODO: omitted — upstream also exports `./proxy`, `./proxy-endpoint`, and `./serverless-cluster`
// here (DatabaseProxy/-Endpoint, ServerlessCluster v1). Those land in a later PR (RDS PR 2e) —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/aws-cdk-lib/aws-rds/lib/index.ts#L15-L17
export * from "./proxy";
export * from "./proxy-endpoint";
export * from "./serverless-cluster";
export * from "./subnet-group";
export * from "./aurora-cluster-instance";

Expand Down
31 changes: 15 additions & 16 deletions src/aws/storage/rds/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import type {
SnapshotCredentials,
} from "./props";
import { Credentials, PerformanceInsightRetention } from "./props";
import type { DatabaseProxyOptions } from "./proxy";
import { DatabaseProxy, ProxyTarget } from "./proxy";
import type { ISubnetGroup } from "./subnet-group";
import { SubnetGroup } from "./subnet-group";
import { validateDatabaseInstanceProps } from "./validate-database-insights";
Expand Down Expand Up @@ -96,10 +98,10 @@ export interface IDatabaseInstance
*/
readonly engine?: IInstanceEngine;

// TODO: omitted — upstream also declares `addProxy(id, options): DatabaseProxy` here. `DatabaseProxy`
// (and the `./proxy` module it lives in) is not ported in this repo yet — it lands in a later PR
// (RDS PR 2e), matching the existing barrel deferral in `./index.ts` —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/aws-cdk-lib/aws-rds/lib/instance.ts#L86-L89
/**
* Add a new db proxy to this instance.
*/
addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy;

/**
* Grant the given identity connection access to the database.
Expand Down Expand Up @@ -292,18 +294,15 @@ export abstract class DatabaseInstanceBase
*/
public abstract readonly connections: ec2.Connections;

// TODO: omitted — see the TODO on `IDatabaseInstance` above; `DatabaseProxy`/`./proxy` is not
// ported yet (RDS PR 2e) —
// https://github.com/aws/aws-cdk/blob/v2.263.0/packages/aws-cdk-lib/aws-rds/lib/instance.ts#L246-L251
// /**
// * Add a new db proxy to this instance.
// */
// public addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy {
// return new DatabaseProxy(this, id, {
// proxyTarget: ProxyTarget.fromInstance(this),
// ...options,
// });
// }
/**
* Add a new db proxy to this instance.
*/
public addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy {
return new DatabaseProxy(this, id, {
proxyTarget: ProxyTarget.fromInstance(this),
...options,
});
}

/**
* [disable-awslint:no-grants]
Expand Down
Loading
Loading