Repository: aws-samples/cdk-keycloak (language: python)

Issue:
Aurora Serverless v2 Support
The serverless configuration in this project is using Aurora Serverless v1.
It would be nice to support Aurora Serverless v2 too.


Discussion / hints:

Fix patch:
diff --git a/API.md b/API.md
index 20307a1..baf023f 100644
--- a/API.md
+++ b/API.md
@@ -87,6 +87,7 @@ new Database(scope: Construct, id: string, props: DatabaseProps)
 * **props** (<code>[DatabaseProps](#cdk-keycloak-databaseprops)</code>)  *No description*
   * **vpc** (<code>[IVpc](#aws-cdk-aws-ec2-ivpc)</code>)  The VPC for the database. 
   * **auroraServerless** (<code>boolean</code>)  enable aurora serverless. __*Default*__: false
+  * **auroraServerlessV2** (<code>boolean</code>)  enable aurora serverless v2. __*Default*__: false
   * **backupRetention** (<code>[Duration](#aws-cdk-core-duration)</code>)  database backup retension. __*Default*__: 7 days
   * **clusterEngine** (<code>[IClusterEngine](#aws-cdk-aws-rds-iclusterengine)</code>)  The database cluster engine. __*Default*__: rds.AuroraMysqlEngineVersion.VER_2_09_1
   * **databaseSubnets** (<code>[SubnetSelection](#aws-cdk-aws-ec2-subnetselection)</code>)  VPC subnets for database. __*Optional*__
@@ -131,6 +132,7 @@ new KeyCloak(scope: Construct, id: string, props: KeyCloakProps)
   * **certificateArn** (<code>string</code>)  ACM certificate ARN to import. 
   * **keycloakVersion** (<code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code>)  The Keycloak version for the cluster. 
   * **auroraServerless** (<code>boolean</code>)  Whether to use aurora serverless. __*Default*__: false
+  * **auroraServerlessV2** (<code>boolean</code>)  Whether to use aurora serverless v2. __*Default*__: false
   * **autoScaleTask** (<code>[AutoScaleTask](#cdk-keycloak-autoscaletask)</code>)  Autoscaling for the ECS Service. __*Default*__: no ecs service autoscaling
   * **backupRetention** (<code>[Duration](#aws-cdk-core-duration)</code>)  database backup retension. __*Default*__: 7 days
   * **bastion** (<code>boolean</code>)  Create a bastion host for debugging or trouble-shooting. __*Default*__: false
@@ -170,6 +172,7 @@ addDatabase(props: DatabaseProps): Database
 * **props** (<code>[DatabaseProps](#cdk-keycloak-databaseprops)</code>)  *No description*
   * **vpc** (<code>[IVpc](#aws-cdk-aws-ec2-ivpc)</code>)  The VPC for the database. 
   * **auroraServerless** (<code>boolean</code>)  enable aurora serverless. __*Default*__: false
+  * **auroraServerlessV2** (<code>boolean</code>)  enable aurora serverless v2. __*Default*__: false
   * **backupRetention** (<code>[Duration](#aws-cdk-core-duration)</code>)  database backup retension. __*Default*__: 7 days
   * **clusterEngine** (<code>[IClusterEngine](#aws-cdk-aws-rds-iclusterengine)</code>)  The database cluster engine. __*Default*__: rds.AuroraMysqlEngineVersion.VER_2_09_1
   * **databaseSubnets** (<code>[SubnetSelection](#aws-cdk-aws-ec2-subnetselection)</code>)  VPC subnets for database. __*Optional*__
@@ -310,6 +313,7 @@ Name | Type | Description
 -----|------|-------------
 **vpc** | <code>[IVpc](#aws-cdk-aws-ec2-ivpc)</code> | The VPC for the database.
 **auroraServerless**? | <code>boolean</code> | enable aurora serverless.<br/>__*Default*__: false
+**auroraServerlessV2**? | <code>boolean</code> | enable aurora serverless v2.<br/>__*Default*__: false
 **backupRetention**? | <code>[Duration](#aws-cdk-core-duration)</code> | database backup retension.<br/>__*Default*__: 7 days
 **clusterEngine**? | <code>[IClusterEngine](#aws-cdk-aws-rds-iclusterengine)</code> | The database cluster engine.<br/>__*Default*__: rds.AuroraMysqlEngineVersion.VER_2_09_1
 **databaseSubnets**? | <code>[SubnetSelection](#aws-cdk-aws-ec2-subnetselection)</code> | VPC subnets for database.<br/>__*Optional*__
@@ -331,6 +335,7 @@ Name | Type | Description
 **certificateArn** | <code>string</code> | ACM certificate ARN to import.
 **keycloakVersion** | <code>[KeycloakVersion](#cdk-keycloak-keycloakversion)</code> | The Keycloak version for the cluster.
 **auroraServerless**? | <code>boolean</code> | Whether to use aurora serverless.<br/>__*Default*__: false
+**auroraServerlessV2**? | <code>boolean</code> | Whether to use aurora serverless v2.<br/>__*Default*__: false
 **autoScaleTask**? | <code>[AutoScaleTask](#cdk-keycloak-autoscaletask)</code> | Autoscaling for the ECS Service.<br/>__*Default*__: no ecs service autoscaling
 **backupRetention**? | <code>[Duration](#aws-cdk-core-duration)</code> | database backup retension.<br/>__*Default*__: 7 days
 **bastion**? | <code>boolean</code> | Create a bastion host for debugging or trouble-shooting.<br/>__*Default*__: false
diff --git a/README.md b/README.md
index 3950bb6..6c785d0 100644
--- a/README.md
+++ b/README.md
@@ -44,11 +44,19 @@ To specify any other verion not defined in the construct, use `KeycloakVersion.o
 The `KeyCloak` construct provisions the **Amaozn RDS cluster for MySQL** with **2** database instances under the hood, to opt in **Amazon Aurora Serverless**, use `auroraServerless` to opt in Amazon Aurora Serverless cluster. Please note only some regions are supported, check [Supported features in Amazon Aurora by AWS Region and Aurora DB engine](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.AuroraFeaturesRegionsDBEngines.grids.html) for availability.
 
 ```ts
+// Aurora Serverless v1
 new KeyCloak(stack, 'KeyCloak', {
   certificateArn,
   auroraServerless: true,
   keycloakVersion,
 });
+
+// Aurora Serverless v2
+new KeyCloak(stack, 'KeyCloak', {
+  certificateArn,
+  auroraServerlessV2: true,
+  keycloakVersion,
+});
 ```
 
 Behind the scene, a default RDS cluster for MySQL with 2 database instances will be created.
diff --git a/src/keycloak.ts b/src/keycloak.ts
index c125aff..6935bc6 100644
--- a/src/keycloak.ts
+++ b/src/keycloak.ts
@@ -174,6 +174,13 @@ export interface KeyCloakProps {
    * @default false
    */
   readonly auroraServerless?: boolean;
+  /**
+   * Whether to use aurora serverless v2. When enabled, the `databaseInstanceType` and
+   * `engine` will be ignored.
+   *
+   * @default false
+   */
+  readonly auroraServerlessV2?: boolean;
   /**
    * Whether to use single RDS instance rather than RDS cluster. Not recommended for production.
    *
@@ -221,6 +228,7 @@ export class KeyCloak extends cdk.Construct {
       instanceEngine: props.instanceEngine,
       clusterEngine: props.clusterEngine,
       auroraServerless: props.auroraServerless,
+      auroraServerlessV2: props.auroraServerlessV2,
       singleDbInstance: props.singleDbInstance,
       backupRetention: props.backupRetention,
     });
@@ -293,6 +301,12 @@ export interface DatabaseProps {
    * @default false
    */
   readonly auroraServerless?: boolean;
+  /**
+   * enable aurora serverless v2
+   *
+   * @default false
+   */
+  readonly auroraServerlessV2?: boolean;
 
   /**
    * Whether to use single RDS instance rather than RDS cluster. Not recommended for production.
@@ -344,8 +358,16 @@ export class Database extends cdk.Construct {
   constructor(scope: cdk.Construct, id: string, props: DatabaseProps) {
     super(scope, id);
     this.vpc = props.vpc;
-    const config = props.auroraServerless ? this._createServerlessCluster(props)
-      : props.singleDbInstance ? this._createRdsInstance(props) : this._createRdsCluster(props);
+    let config;
+    if (props.auroraServerless) {
+      config = this._createServerlessCluster(props);
+    } else if (props.auroraServerlessV2) {
+      config = this._createServerlessV2Cluster(props);
+    } else if (props.singleDbInstance) {
+      config = this._createRdsInstance(props);
+    } else {
+      config = this._createRdsCluster(props);
+    }
     this.secret = config.secret;
     // allow internally from the same security group
     config.connections.allowInternally(ec2.Port.tcp(this._mysqlListenerPort));
@@ -428,6 +450,46 @@ export class Database extends cdk.Construct {
       secret: dbCluster.secret!,
     };
   }
+  // create a RDS for MySQL DB cluster with Aurora Serverless v2
+  private _createServerlessV2Cluster(props: DatabaseProps): DatabaseCofig {
+    const dbCluster = new rds.DatabaseCluster(this, 'DBCluster', {
+      engine: rds.DatabaseClusterEngine.auroraMysql({
+        // Engine Version Manually Specified since Aurora MySQL 3.02.0 is not listed in CDK v1 currently
+        version: { auroraMysqlFullVersion: '8.0.mysql_aurora.3.02.0', auroraMysqlMajorVersion: '8.0', _combineImportAndExportRoles: true } as rds.AuroraMysqlEngineVersion,
+      }),
+      defaultDatabaseName: 'keycloak',
+      deletionProtection: true,
+      credentials: rds.Credentials.fromGeneratedSecret('admin'),
+      instanceProps: {
+        vpc: props.vpc,
+        vpcSubnets: props.databaseSubnets,
+        // Specify serverless Instance Type
+        instanceType: new ec2.InstanceType('serverless'),
+      },
+      // Set default parameter group for Aurora MySQL 8.0
+      parameterGroup: rds.ParameterGroup.fromParameterGroupName(this, 'ParameterGroup', 'default.aurora-mysql8.0'),
+      backup: {
+        retention: props.backupRetention ?? cdk.Duration.days(7),
+      },
+      storageEncrypted: true,
+      removalPolicy: cdk.RemovalPolicy.RETAIN,
+    });
+    // Set Serverless V2 Scaling Configuration
+    // TODO: Use cleaner way to set scaling configuration.
+    // https://github.com/aws/aws-cdk/issues/20197
+    (
+      dbCluster.node.findChild('Resource') as rds.CfnDBCluster
+    ).serverlessV2ScalingConfiguration = {
+      minCapacity: 0.5,
+      maxCapacity: 10,
+    };
+    return {
+      connections: dbCluster.connections,
+      endpoint: dbCluster.clusterEndpoint.hostname,
+      identifier: dbCluster.clusterIdentifier,
+      secret: dbCluster.secret!,
+    };
+  }
 }
 
 export interface ContainerServiceProps {


Test patch:
diff --git a/test/cluster.test.ts b/test/cluster.test.ts
index 7b548d7..4d01fb5 100644
--- a/test/cluster.test.ts
+++ b/test/cluster.test.ts
@@ -169,6 +169,110 @@ test('with aurora serverless', () => {
   });
 });
 
+test('with aurora serverless v2', () => {
+
+  // GIVEN
+  const app = new App();
+  const stack = new Stack(app, 'testing-stack');
+
+  // WHEN
+  new kc.KeyCloak(stack, 'KeyCloak', {
+    certificateArn: 'MOCK_ARN',
+    auroraServerlessV2: true,
+    keycloakVersion: KeycloakVersion.V15_0_2,
+  });
+
+  // THEN
+  expect(stack).toHaveResource('AWS::RDS::DBCluster', {
+    Engine: 'aurora-mysql',
+    DBClusterParameterGroupName: 'default.aurora-mysql8.0',
+    DBSubnetGroupName: {
+      Ref: 'KeyCloakDatabaseDBClusterSubnetsE36F1B1B',
+    },
+    EngineVersion: '8.0.mysql_aurora.3.02.0',
+    MasterUsername: 'admin',
+    MasterUserPassword: {
+      'Fn::Join': [
+        '',
+        [
+          '{{resolve:secretsmanager:',
+          {
+            Ref: 'testingstackKeyCloakDatabaseDBClusterSecret754146743fdaad7efa858a3daf9490cf0a702aeb',
+          },
+          ':SecretString:password::}}',
+        ],
+      ],
+    },
+    ServerlessV2ScalingConfiguration: {
+      MaxCapacity: 10,
+      MinCapacity: 0.5,
+    },
+    VpcSecurityGroupIds: [
+      {
+        'Fn::GetAtt': [
+          'KeyCloakDatabaseDBClusterSecurityGroup843B4392',
+          'GroupId',
+        ],
+      },
+    ],
+  });
+  // we should have 2 db instances in the cluster
+  expect(stack).toCountResources('AWS::RDS::DBInstance', 2);
+  // we should have db instance with db.serverless instance class
+  expect(stack).toHaveResource('AWS::RDS::DBInstance', {
+    DBInstanceClass: 'db.serverless',
+  });
+  // we should have 2 secrets
+  expect(stack).toCountResources('AWS::SecretsManager::Secret', 2);
+  // we should have ecs service
+  expect(stack).toHaveResource('AWS::ECS::Service', {
+    Cluster: {
+      Ref: 'KeyCloakKeyCloakContainerSerivceClusterA18E44FF',
+    },
+    DeploymentConfiguration: {
+      MaximumPercent: 200,
+      MinimumHealthyPercent: 50,
+    },
+    DesiredCount: 2,
+    EnableECSManagedTags: false,
+    HealthCheckGracePeriodSeconds: 120,
+    LaunchType: 'FARGATE',
+    LoadBalancers: [
+      {
+        ContainerName: 'keycloak',
+        ContainerPort: 8443,
+        TargetGroupArn: {
+          Ref: 'KeyCloakKeyCloakContainerSerivceALBHttpsListenerECSTargetGroupCE3EF52C',
+        },
+      },
+    ],
+    NetworkConfiguration: {
+      AwsvpcConfiguration: {
+        AssignPublicIp: 'DISABLED',
+        SecurityGroups: [
+          {
+            'Fn::GetAtt': [
+              'KeyCloakKeyCloakContainerSerivceServiceSecurityGroup4C80023D',
+              'GroupId',
+            ],
+          },
+        ],
+        Subnets: [
+          {
+            Ref: 'KeyCloakVpcPrivateSubnet1SubnetA692DFFF',
+          },
+          {
+            Ref: 'KeyCloakVpcPrivateSubnet2SubnetC8682D75',
+          },
+        ],
+      },
+    },
+    TaskDefinition: {
+      Ref: 'KeyCloakKeyCloakContainerSerivceTaskDef30C9533A',
+    },
+  });
+});
+
 test('with single rds instance', () => {
 
   // GIVEN