diff --git a/packages/@aws-cdk/aws-redshift-alpha/awslint.json b/packages/@aws-cdk/aws-redshift-alpha/awslint.json index 5b6a3269ed31a..ae3fd0684e095 100644 --- a/packages/@aws-cdk/aws-redshift-alpha/awslint.json +++ b/packages/@aws-cdk/aws-redshift-alpha/awslint.json @@ -4,6 +4,9 @@ "props-physical-name:@aws-cdk/aws-redshift-alpha.ClusterSubnetGroupProps", "props-physical-name:@aws-cdk/aws-redshift-alpha.DatabaseSecretProps", "attribute-tag:@aws-cdk/aws-redshift-alpha.DatabaseSecret.secretName", - "attribute-tag:@aws-cdk/aws-redshift-alpha.DatabaseSecret.secretFullArn" + "attribute-tag:@aws-cdk/aws-redshift-alpha.DatabaseSecret.secretFullArn", + "props-no-arn-refs:@aws-cdk/aws-redshift-alpha.RedshiftQuerySchedulerProps.clusterArn", + "props-no-arn-refs:@aws-cdk/aws-redshift-alpha.RedshiftQuerySchedulerProps.workgroupArn", + "props-no-arn-refs:@aws-cdk/aws-redshift-alpha.RedshiftQuerySchedulerProps.secretArn" ] } diff --git a/packages/@aws-cdk/aws-redshift-alpha/lib/index.ts b/packages/@aws-cdk/aws-redshift-alpha/lib/index.ts index b905682bcf03c..d235a7de0fdba 100644 --- a/packages/@aws-cdk/aws-redshift-alpha/lib/index.ts +++ b/packages/@aws-cdk/aws-redshift-alpha/lib/index.ts @@ -3,6 +3,7 @@ export * from './parameter-group'; export * from './database-options'; export * from './database-secret'; export * from './endpoint'; +export * from './redshift-query-scheduler'; export * from './subnet-group'; export * from './table'; export * from './user'; diff --git a/packages/@aws-cdk/aws-redshift-alpha/lib/redshift-query-scheduler.ts b/packages/@aws-cdk/aws-redshift-alpha/lib/redshift-query-scheduler.ts new file mode 100644 index 0000000000000..14e7e0efcfdbc --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/lib/redshift-query-scheduler.ts @@ -0,0 +1,202 @@ +// ABOUTME: Ultra-simplified RedshiftQueryScheduler using only scheduled EventBridge Rule +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as scheduler from 'aws-cdk-lib/aws-scheduler'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import { ValidationError } from 'aws-cdk-lib/core'; + +/** + * Properties for the RedshiftQueryScheduler construct. + */ +export interface RedshiftQuerySchedulerProps { + /** + * The name of the scheduler. This will be used to create the EventBridge Scheduler rule name + * and the StatementName in RedshiftDataParameters, both prefixed with "QS2-". + */ + readonly schedulerName: string; + + /** + * The name of the Redshift database to execute queries against. + */ + readonly database: string; + + /** + * The ARN of the Redshift cluster. Cannot be used together with workgroupArn. + * + * @default - No cluster ARN specified + */ + readonly clusterArn?: string; + + /** + * The ARN of the Redshift Serverless workgroup. Cannot be used together with clusterArn. + * When using workgroupArn, dbUser cannot be specified. + * + * @default - No workgroup ARN specified + */ + readonly workgroupArn?: string; + + /** + * The database user name for authentication. Cannot be used together with secretArn. + * Cannot be used when workgroupArn is specified. + * + * @default - No database user specified + */ + readonly dbUser?: string; + + /** + * The ARN of the secret containing database credentials. Cannot be used together with dbUser. + * + * @default - No secret ARN specified + */ + readonly secretArn?: string; + + /** + * A single SQL statement to execute. Cannot be used together with sqls. + * + * @default - No single SQL statement specified + */ + readonly sql?: string; + + /** + * Multiple SQL statements to execute as a transaction. Cannot be used together with sql. + * + * @default - No SQL statements array specified + */ + readonly sqls?: string[]; + + /** + * The schedule expression that defines when the query should be executed. + */ + readonly schedule: scheduler.ScheduleExpression; + + /** + * A description for the scheduler. + * + * @default - No description + */ + readonly description?: string; + + /** + * Whether the scheduler is enabled. + * + * @default true + */ + readonly enabled?: boolean; + + /** + * The IAM role to use for executing the Redshift query. + * This role must have permissions to execute queries on the specified Redshift cluster or workgroup. + * + * @default - A new role will be created with the necessary permissions + */ + readonly role?: iam.IRole; +} + +/** + * A construct that creates a scheduled EventBridge Rule to execute Redshift queries using the Redshift Data API. + * + * This construct uses the simplest possible approach: a single EventBridge Rule with a schedule + * that directly targets RedshiftQuery. No EventBridge Scheduler needed. + * + * The EventBridge rule name and StatementName are both set to "QS2-{schedulerName}" to enable + * identification in the Amazon Redshift console. + * + * @example + * new RedshiftQueryScheduler(this, 'MyScheduler', { + * schedulerName: 'daily-report', + * database: 'analytics', + * clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:my-cluster', + * dbUser: 'scheduler_user', + * sql: 'CALL generate_daily_report()', + * schedule: ScheduleExpression.rate(Duration.days(1)), + * }); + */ +export class RedshiftQueryScheduler extends Construct { + /** + * The EventBridge Rule created by this construct. + */ + public readonly rule: events.Rule; + + constructor(scope: Construct, id: string, props: RedshiftQuerySchedulerProps) { + super(scope, id); + + // Validate input parameters + this.validateProps(props); + + const ruleName = `QS2-${props.schedulerName}`; + const statementName = `QS2-${props.schedulerName}`; + const targetArn = props.clusterArn || props.workgroupArn!; + + // Create scheduled EventBridge Rule + this.rule = new events.Rule(this, 'Rule', { + ruleName: ruleName, + schedule: events.Schedule.expression(props.schedule.expressionString), + description: props.description, + enabled: props.enabled ?? true, + }); + + // Create RedshiftQuery target with parameter mapping + const redshiftQueryProps: targets.RedshiftQueryProps = { + database: props.database, + dbUser: props.dbUser, + secret: props.secretArn ? secretsmanager.Secret.fromSecretCompleteArn(this, 'Secret', props.secretArn) : undefined, + sql: props.sql ? [props.sql] : props.sqls!, + statementName: statementName, + sendEventBridgeEvent: true, // Maps to WithEvent: true + role: props.role, + }; + + const redshiftTarget = new targets.RedshiftQuery(targetArn, redshiftQueryProps); + this.rule.addTarget(redshiftTarget); + } + + private validateProps(props: RedshiftQuerySchedulerProps): void { + // Validate target ARN (cluster or workgroup) + const hasCluster = !!props.clusterArn; + const hasWorkgroup = !!props.workgroupArn; + + if (hasCluster && hasWorkgroup) { + throw new ValidationError('Cannot specify both clusterArn and workgroupArn. Choose exactly one.', this); + } + + if (!hasCluster && !hasWorkgroup) { + throw new ValidationError('Must specify exactly one of clusterArn or workgroupArn.', this); + } + + // Validate authentication + const hasDbUser = !!props.dbUser; + const hasSecretArn = !!props.secretArn; + + if (hasDbUser && hasSecretArn) { + throw new ValidationError('Cannot specify both dbUser and secretArn. Choose exactly one.', this); + } + + // Validate workgroup + dbUser restriction + if (hasWorkgroup && hasDbUser) { + throw new ValidationError('Cannot specify dbUser when using workgroupArn.', this); + } + + // Validate SQL content first + if (props.sql !== undefined && props.sql.trim() === '') { + throw new ValidationError('sql cannot be empty.', this); + } + + if (props.sqls !== undefined && props.sqls.length === 0) { + throw new ValidationError('sqls array cannot be empty.', this); + } + + // Validate SQL presence + const hasSql = !!props.sql && props.sql.trim() !== ''; + const hasSqls = !!props.sqls && props.sqls.length > 0; + + if (hasSql && hasSqls) { + throw new ValidationError('Cannot specify both sql and sqls. Choose exactly one.', this); + } + + if (!hasSql && !hasSqls) { + throw new ValidationError('Must specify exactly one of sql or sqls.', this); + } + } +} diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets.json new file mode 100644 index 0000000000000..94e5abeae122c --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets.json @@ -0,0 +1,20 @@ +{ + "version": "45.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F Template", + "source": { + "path": "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.template.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/aws-redshift-alpha-query-scheduler.assets.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/aws-redshift-alpha-query-scheduler.assets.json new file mode 100644 index 0000000000000..a71c9b29241ec --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/aws-redshift-alpha-query-scheduler.assets.json @@ -0,0 +1,21 @@ +{ + "version": "45.0.0", + "files": { + "f27dadc20a85045fc9be37fb3ace65041eaceb01056d77d611b893ca42ae9d7d": { + "displayName": "aws-redshift-alpha-query-scheduler Template", + "source": { + "path": "aws-redshift-alpha-query-scheduler.template.json", + "packaging": "file" + }, + "destinations": { + "123456789012-us-east-1-c1b676f7": { + "bucketName": "cdk-hnb659fds-assets-123456789012-us-east-1", + "objectKey": "f27dadc20a85045fc9be37fb3ace65041eaceb01056d77d611b893ca42ae9d7d.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-file-publishing-role-123456789012-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/aws-redshift-alpha-query-scheduler.template.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/aws-redshift-alpha-query-scheduler.template.json new file mode 100644 index 0000000000000..fab1f7c0c83fd --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/aws-redshift-alpha-query-scheduler.template.json @@ -0,0 +1,316 @@ +{ + "Resources": { + "ClusterSchedulerRule433DC541": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": "Daily order count report for cluster", + "Name": "QS2-cluster-daily-report", + "ScheduleExpression": "rate(1 day)", + "State": "ENABLED", + "Targets": [ + { + "Arn": "arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster", + "Id": "Target0", + "RedshiftDataParameters": { + "Database": "analytics", + "DbUser": "scheduler_user", + "Sql": "SELECT COUNT(*) FROM orders WHERE created_date = CURRENT_DATE", + "StatementName": "QS2-cluster-daily-report", + "WithEvent": true + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterSchedulerRuleEventsRole346D0951", + "Arn" + ] + } + } + ] + } + }, + "ClusterSchedulerRuleEventsRole346D0951": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ClusterSchedulerRuleEventsRoleDefaultPolicy9DF76113": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "redshift-data:ExecuteStatement", + "Effect": "Allow", + "Resource": "arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ClusterSchedulerRuleEventsRoleDefaultPolicy9DF76113", + "Roles": [ + { + "Ref": "ClusterSchedulerRuleEventsRole346D0951" + } + ] + } + }, + "WorkgroupSchedulerRule89FA52DD": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": "Hourly cleanup for workgroup", + "Name": "QS2-workgroup-hourly-cleanup", + "ScheduleExpression": "rate(1 hour)", + "State": "ENABLED", + "Targets": [ + { + "Arn": "arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup", + "Id": "Target0", + "RedshiftDataParameters": { + "Database": "warehouse", + "SecretManagerArn": "arn:aws:secretsmanager:us-east-1:123456789012:secret:redshift-credentials-abc123", + "Sqls": [ + "DELETE FROM temp_table WHERE created_at < CURRENT_TIMESTAMP - INTERVAL '1 hour'", + "VACUUM temp_table", + "ANALYZE temp_table" + ], + "StatementName": "QS2-workgroup-hourly-cleanup", + "WithEvent": true + }, + "RoleArn": { + "Fn::GetAtt": [ + "WorkgroupSchedulerRuleEventsRole36C21F42", + "Arn" + ] + } + } + ] + } + }, + "WorkgroupSchedulerRuleEventsRole36C21F42": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "WorkgroupSchedulerRuleEventsRoleDefaultPolicyD52F34D7": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "redshift-data:BatchExecuteStatement", + "Effect": "Allow", + "Resource": "arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "WorkgroupSchedulerRuleEventsRoleDefaultPolicyD52F34D7", + "Roles": [ + { + "Ref": "WorkgroupSchedulerRuleEventsRole36C21F42" + } + ] + } + }, + "DisabledSchedulerRuleDF088B11": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": "Disabled scheduler for testing", + "Name": "QS2-disabled-test", + "ScheduleExpression": "rate(30 minutes)", + "State": "DISABLED", + "Targets": [ + { + "Arn": "arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster-2", + "Id": "Target0", + "RedshiftDataParameters": { + "Database": "test_db", + "DbUser": "test_user", + "Sql": "SELECT 1", + "StatementName": "QS2-disabled-test", + "WithEvent": true + }, + "RoleArn": { + "Fn::GetAtt": [ + "DisabledSchedulerRuleEventsRoleAFECDCB4", + "Arn" + ] + } + } + ] + } + }, + "DisabledSchedulerRuleEventsRoleAFECDCB4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "DisabledSchedulerRuleEventsRoleDefaultPolicyED0B743B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "redshift-data:ExecuteStatement", + "Effect": "Allow", + "Resource": "arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster-2" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "DisabledSchedulerRuleEventsRoleDefaultPolicyED0B743B", + "Roles": [ + { + "Ref": "DisabledSchedulerRuleEventsRoleAFECDCB4" + } + ] + } + }, + "CustomRedshiftRole1EB1A91A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Description": "Custom role for Redshift query execution" + } + }, + "CustomRedshiftRoleDefaultPolicy85816695": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "redshift-data:DescribeStatement", + "redshift-data:ExecuteStatement", + "redshift-data:GetStatementResult" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "redshift-data:ExecuteStatement", + "Effect": "Allow", + "Resource": "arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomRedshiftRoleDefaultPolicy85816695", + "Roles": [ + { + "Ref": "CustomRedshiftRole1EB1A91A" + } + ] + } + }, + "CustomRoleSchedulerRuleE4CDC4C6": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": "Scheduler with custom IAM role", + "Name": "QS2-custom-role-test", + "ScheduleExpression": "rate(12 hours)", + "State": "ENABLED", + "Targets": [ + { + "Arn": "arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster", + "Id": "Target0", + "RedshiftDataParameters": { + "Database": "analytics", + "DbUser": "scheduler_user", + "Sql": "SELECT COUNT(*) FROM users", + "StatementName": "QS2-custom-role-test", + "WithEvent": true + }, + "RoleArn": { + "Fn::GetAtt": [ + "CustomRedshiftRole1EB1A91A", + "Arn" + ] + } + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/cdk.out b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/cdk.out new file mode 100644 index 0000000000000..3704a1b682acf --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"45.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/integ.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/integ.json new file mode 100644 index 0000000000000..03ac0490026f4 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "45.0.0", + "testCases": { + "RedshiftQuerySchedulerIntegTest/DefaultTest": { + "stacks": [ + "aws-redshift-alpha-query-scheduler" + ], + "assertionStack": "RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert", + "assertionStackName": "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F" + } + }, + "minimumCliVersion": "2.1020.2" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/manifest.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/manifest.json new file mode 100644 index 0000000000000..81a276db262c3 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/manifest.json @@ -0,0 +1,1077 @@ +{ + "version": "45.0.0", + "artifacts": { + "aws-redshift-alpha-query-scheduler.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-redshift-alpha-query-scheduler.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-redshift-alpha-query-scheduler": { + "type": "aws:cloudformation:stack", + "environment": "aws://123456789012/us-east-1", + "properties": { + "templateFile": "aws-redshift-alpha-query-scheduler.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-deploy-role-123456789012-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-cfn-exec-role-123456789012-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-123456789012-us-east-1/f27dadc20a85045fc9be37fb3ace65041eaceb01056d77d611b893ca42ae9d7d.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-redshift-alpha-query-scheduler.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-lookup-role-123456789012-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-redshift-alpha-query-scheduler.assets" + ], + "metadata": { + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "ruleName": "*", + "schedule": "*", + "description": "*", + "enabled": true + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addEventPattern": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addTarget": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterSchedulerRule433DC541" + } + ], + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "roleName": "*", + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/ImportEventsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterSchedulerRuleEventsRole346D0951" + } + ], + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ClusterSchedulerRuleEventsRoleDefaultPolicy9DF76113" + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "ruleName": "*", + "schedule": "*", + "description": "*", + "enabled": true + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addEventPattern": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addTarget": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WorkgroupSchedulerRule89FA52DD" + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "roleName": "*", + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/ImportEventsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WorkgroupSchedulerRuleEventsRole36C21F42" + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WorkgroupSchedulerRuleEventsRoleDefaultPolicyD52F34D7" + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "ruleName": "*", + "schedule": "*", + "description": "*", + "enabled": false + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addEventPattern": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addTarget": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DisabledSchedulerRuleDF088B11" + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "roleName": "*", + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/ImportEventsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DisabledSchedulerRuleEventsRoleAFECDCB4" + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DisabledSchedulerRuleEventsRoleDefaultPolicyED0B743B" + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRedshiftRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "description": "*" + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRedshiftRole/ImportCustomRedshiftRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRedshiftRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomRedshiftRole1EB1A91A" + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRedshiftRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRedshiftRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomRedshiftRoleDefaultPolicy85816695" + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRoleScheduler/Rule": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "ruleName": "*", + "schedule": "*", + "description": "*", + "enabled": true + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addEventPattern": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addTarget": [ + {} + ] + } + } + ], + "/aws-redshift-alpha-query-scheduler/CustomRoleScheduler/Rule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomRoleSchedulerRuleE4CDC4C6" + } + ], + "/aws-redshift-alpha-query-scheduler/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-redshift-alpha-query-scheduler/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ], + "CDKMetadata": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadata", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } + ] + }, + "displayName": "aws-redshift-alpha-query-scheduler" + }, + "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "RedshiftQuerySchedulerIntegTestDefaultTestDeployAssert50D0F59F.assets" + ], + "metadata": { + "/RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/core:enableStackNameDuplicates": { + "recommendedValue": true, + "explanation": "Allow multiple stacks with the same name" + }, + "aws-cdk:enableDiffNoFail": { + "recommendedValue": true, + "explanation": "Make `cdk diff` not fail when there are differences" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD" + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path" + }, + "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": { + "recommendedValue": true, + "explanation": "DockerImageAsset properly supports `.dockerignore` files by default" + }, + "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": { + "recommendedValue": true, + "explanation": "Fix the referencing of SecretsManager names from ARNs" + }, + "@aws-cdk/aws-kms:defaultKeyPolicies": { + "recommendedValue": true, + "explanation": "Tighten default KMS key policies" + }, + "@aws-cdk/aws-s3:grantWriteWithoutAcl": { + "recommendedValue": true, + "explanation": "Remove `PutObjectAcl` from Bucket.grantWrite" + }, + "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": { + "recommendedValue": true, + "explanation": "Do not specify a default DesiredCount for ECS services" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK" + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently" + }, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have elastic file systems encrypted at rest by default." + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default." + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy" + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model." + }, + "@aws-cdk/core:explicitStackTags": { + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis." + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy" + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role" + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + } + } + } + } + }, + "minimumCliVersion": "2.1020.2" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/tree.json b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/tree.json new file mode 100644 index 0000000000000..00bd7f32b3994 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-redshift-alpha-query-scheduler":{"id":"aws-redshift-alpha-query-scheduler","path":"aws-redshift-alpha-query-scheduler","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"ClusterScheduler":{"id":"ClusterScheduler","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler","constructInfo":{"fqn":"@aws-cdk/aws-redshift-alpha.RedshiftQueryScheduler","version":"0.0.0"},"children":{"Rule":{"id":"Rule","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule","constructInfo":{"fqn":"aws-cdk-lib.aws_events.Rule","version":"0.0.0","metadata":[{"ruleName":"*","schedule":"*","description":"*","enabled":true},{"addEventPattern":["*"]},{"addTarget":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_events.CfnRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Events::Rule","aws:cdk:cloudformation:props":{"description":"Daily order count report for cluster","name":"QS2-cluster-daily-report","scheduleExpression":"rate(1 day)","state":"ENABLED","targets":[{"id":"Target0","arn":"arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster","roleArn":{"Fn::GetAtt":["ClusterSchedulerRuleEventsRole346D0951","Arn"]},"redshiftDataParameters":{"database":"analytics","dbUser":"scheduler_user","sql":"SELECT COUNT(*) FROM orders WHERE created_date = CURRENT_DATE","statementName":"QS2-cluster-daily-report","withEvent":true}}]}}},"EventsRole":{"id":"EventsRole","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"roleName":"*","assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportEventsRole":{"id":"ImportEventsRole","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/ImportEventsRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"events.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/ClusterScheduler/Rule/EventsRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"redshift-data:ExecuteStatement","Effect":"Allow","Resource":"arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster"}],"Version":"2012-10-17"},"policyName":"ClusterSchedulerRuleEventsRoleDefaultPolicy9DF76113","roles":[{"Ref":"ClusterSchedulerRuleEventsRole346D0951"}]}}}}}}}}}}},"WorkgroupScheduler":{"id":"WorkgroupScheduler","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler","constructInfo":{"fqn":"@aws-cdk/aws-redshift-alpha.RedshiftQueryScheduler","version":"0.0.0"},"children":{"Rule":{"id":"Rule","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule","constructInfo":{"fqn":"aws-cdk-lib.aws_events.Rule","version":"0.0.0","metadata":[{"ruleName":"*","schedule":"*","description":"*","enabled":true},{"addEventPattern":["*"]},{"addTarget":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_events.CfnRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Events::Rule","aws:cdk:cloudformation:props":{"description":"Hourly cleanup for workgroup","name":"QS2-workgroup-hourly-cleanup","scheduleExpression":"rate(1 hour)","state":"ENABLED","targets":[{"id":"Target0","arn":"arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup","roleArn":{"Fn::GetAtt":["WorkgroupSchedulerRuleEventsRole36C21F42","Arn"]},"redshiftDataParameters":{"database":"warehouse","secretManagerArn":"arn:aws:secretsmanager:us-east-1:123456789012:secret:redshift-credentials-abc123","sqls":["DELETE FROM temp_table WHERE created_at < CURRENT_TIMESTAMP - INTERVAL '1 hour'","VACUUM temp_table","ANALYZE temp_table"],"statementName":"QS2-workgroup-hourly-cleanup","withEvent":true}}]}}},"EventsRole":{"id":"EventsRole","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"roleName":"*","assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportEventsRole":{"id":"ImportEventsRole","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/ImportEventsRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"events.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Rule/EventsRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"redshift-data:BatchExecuteStatement","Effect":"Allow","Resource":"arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup"}],"Version":"2012-10-17"},"policyName":"WorkgroupSchedulerRuleEventsRoleDefaultPolicyD52F34D7","roles":[{"Ref":"WorkgroupSchedulerRuleEventsRole36C21F42"}]}}}}}}}}},"Secret":{"id":"Secret","path":"aws-redshift-alpha-query-scheduler/WorkgroupScheduler/Secret","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}}}},"DisabledScheduler":{"id":"DisabledScheduler","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler","constructInfo":{"fqn":"@aws-cdk/aws-redshift-alpha.RedshiftQueryScheduler","version":"0.0.0"},"children":{"Rule":{"id":"Rule","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule","constructInfo":{"fqn":"aws-cdk-lib.aws_events.Rule","version":"0.0.0","metadata":[{"ruleName":"*","schedule":"*","description":"*","enabled":false},{"addEventPattern":["*"]},{"addTarget":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_events.CfnRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Events::Rule","aws:cdk:cloudformation:props":{"description":"Disabled scheduler for testing","name":"QS2-disabled-test","scheduleExpression":"rate(30 minutes)","state":"DISABLED","targets":[{"id":"Target0","arn":"arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster-2","roleArn":{"Fn::GetAtt":["DisabledSchedulerRuleEventsRoleAFECDCB4","Arn"]},"redshiftDataParameters":{"database":"test_db","dbUser":"test_user","sql":"SELECT 1","statementName":"QS2-disabled-test","withEvent":true}}]}}},"EventsRole":{"id":"EventsRole","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"roleName":"*","assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportEventsRole":{"id":"ImportEventsRole","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/ImportEventsRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"events.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/DisabledScheduler/Rule/EventsRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"redshift-data:ExecuteStatement","Effect":"Allow","Resource":"arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster-2"}],"Version":"2012-10-17"},"policyName":"DisabledSchedulerRuleEventsRoleDefaultPolicyED0B743B","roles":[{"Ref":"DisabledSchedulerRuleEventsRoleAFECDCB4"}]}}}}}}}}}}},"CustomRedshiftRole":{"id":"CustomRedshiftRole","path":"aws-redshift-alpha-query-scheduler/CustomRedshiftRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"description":"*"},{"addToPolicy":[{}]},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]},{"addToPrincipalPolicy":[{}]}]},"children":{"ImportCustomRedshiftRole":{"id":"ImportCustomRedshiftRole","path":"aws-redshift-alpha-query-scheduler/CustomRedshiftRole/ImportCustomRedshiftRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/CustomRedshiftRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"events.amazonaws.com"}}],"Version":"2012-10-17"},"description":"Custom role for Redshift query execution"}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-redshift-alpha-query-scheduler/CustomRedshiftRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/CustomRedshiftRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":["redshift-data:DescribeStatement","redshift-data:ExecuteStatement","redshift-data:GetStatementResult"],"Effect":"Allow","Resource":"*"},{"Action":"redshift-data:ExecuteStatement","Effect":"Allow","Resource":"arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster"}],"Version":"2012-10-17"},"policyName":"CustomRedshiftRoleDefaultPolicy85816695","roles":[{"Ref":"CustomRedshiftRole1EB1A91A"}]}}}}}}},"CustomRoleScheduler":{"id":"CustomRoleScheduler","path":"aws-redshift-alpha-query-scheduler/CustomRoleScheduler","constructInfo":{"fqn":"@aws-cdk/aws-redshift-alpha.RedshiftQueryScheduler","version":"0.0.0"},"children":{"Rule":{"id":"Rule","path":"aws-redshift-alpha-query-scheduler/CustomRoleScheduler/Rule","constructInfo":{"fqn":"aws-cdk-lib.aws_events.Rule","version":"0.0.0","metadata":[{"ruleName":"*","schedule":"*","description":"*","enabled":true},{"addEventPattern":["*"]},{"addTarget":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-redshift-alpha-query-scheduler/CustomRoleScheduler/Rule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_events.CfnRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Events::Rule","aws:cdk:cloudformation:props":{"description":"Scheduler with custom IAM role","name":"QS2-custom-role-test","scheduleExpression":"rate(12 hours)","state":"ENABLED","targets":[{"id":"Target0","arn":"arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster","roleArn":{"Fn::GetAtt":["CustomRedshiftRole1EB1A91A","Arn"]},"redshiftDataParameters":{"database":"analytics","dbUser":"scheduler_user","sql":"SELECT COUNT(*) FROM users","statementName":"QS2-custom-role-test","withEvent":true}}]}}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-redshift-alpha-query-scheduler/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-redshift-alpha-query-scheduler/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"RedshiftQuerySchedulerIntegTest":{"id":"RedshiftQuerySchedulerIntegTest","path":"RedshiftQuerySchedulerIntegTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"RedshiftQuerySchedulerIntegTest/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"RedshiftQuerySchedulerIntegTest/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"RedshiftQuerySchedulerIntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.ts b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.ts new file mode 100644 index 0000000000000..e03fced625a0f --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/integ.redshift-query-scheduler.ts @@ -0,0 +1,90 @@ +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as scheduler from 'aws-cdk-lib/aws-scheduler'; +import { Duration, Stack, App, StackProps } from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import { Construct } from 'constructs'; +import * as redshift from '../lib'; + +class RedshiftQuerySchedulerTestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // Test 1: Cluster with dbUser and single SQL + new redshift.RedshiftQueryScheduler(this, 'ClusterScheduler', { + schedulerName: 'cluster-daily-report', + database: 'analytics', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'scheduler_user', + sql: 'SELECT COUNT(*) FROM orders WHERE created_date = CURRENT_DATE', + schedule: scheduler.ScheduleExpression.rate(Duration.days(1)), + description: 'Daily order count report for cluster', + }); + + // Test 2: Workgroup with secretArn and multiple SQLs + new redshift.RedshiftQueryScheduler(this, 'WorkgroupScheduler', { + schedulerName: 'workgroup-hourly-cleanup', + database: 'warehouse', + workgroupArn: 'arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup', + secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:redshift-credentials-abc123', + sqls: [ + 'DELETE FROM temp_table WHERE created_at < CURRENT_TIMESTAMP - INTERVAL \'1 hour\'', + 'VACUUM temp_table', + 'ANALYZE temp_table', + ], + schedule: scheduler.ScheduleExpression.rate(Duration.hours(1)), + description: 'Hourly cleanup for workgroup', + enabled: true, + }); + + // Test 3: Disabled scheduler + new redshift.RedshiftQueryScheduler(this, 'DisabledScheduler', { + schedulerName: 'disabled-test', + database: 'test_db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster-2', + dbUser: 'test_user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(30)), + description: 'Disabled scheduler for testing', + enabled: false, + }); + + // Test 4: Custom IAM role + const customRole = new iam.Role(this, 'CustomRedshiftRole', { + assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), + description: 'Custom role for Redshift query execution', + }); + + customRole.addToPolicy(new iam.PolicyStatement({ + actions: [ + 'redshift-data:ExecuteStatement', + 'redshift-data:DescribeStatement', + 'redshift-data:GetStatementResult', + ], + resources: ['*'], + })); + + new redshift.RedshiftQueryScheduler(this, 'CustomRoleScheduler', { + schedulerName: 'custom-role-test', + database: 'analytics', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'scheduler_user', + sql: 'SELECT COUNT(*) FROM users', + schedule: scheduler.ScheduleExpression.rate(Duration.hours(12)), + description: 'Scheduler with custom IAM role', + role: customRole, + }); + } +} + +const app = new App(); + +const stack = new RedshiftQuerySchedulerTestStack(app, 'aws-redshift-alpha-query-scheduler', { + env: { + account: '123456789012', + region: 'us-east-1', + }, +}); + +new integ.IntegTest(app, 'RedshiftQuerySchedulerIntegTest', { + testCases: [stack], +}); diff --git a/packages/@aws-cdk/aws-redshift-alpha/test/redshift-query-scheduler.test.ts b/packages/@aws-cdk/aws-redshift-alpha/test/redshift-query-scheduler.test.ts new file mode 100644 index 0000000000000..6428e8a772b8e --- /dev/null +++ b/packages/@aws-cdk/aws-redshift-alpha/test/redshift-query-scheduler.test.ts @@ -0,0 +1,379 @@ +// ABOUTME: Unit tests for ultra-simplified RedshiftQueryScheduler construct +// ABOUTME: Tests scheduled EventBridge Rule with RedshiftQuery target - no EventBridge Scheduler needed +import { Template } from 'aws-cdk-lib/assertions'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as scheduler from 'aws-cdk-lib/aws-scheduler'; +import { Duration, Stack } from 'aws-cdk-lib/core'; +import { RedshiftQueryScheduler } from '../lib/redshift-query-scheduler'; + +describe('RedshiftQueryScheduler', () => { + let stack: Stack; + + beforeEach(() => { + stack = new Stack(undefined, 'TestStack', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + }); + + describe('Basic functionality', () => { + test('creates scheduled rule with cluster ARN and dbUser', () => { + // ARRANGE + const props = { + schedulerName: 'test-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: 'SELECT * FROM test_table', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT + new RedshiftQueryScheduler(stack, 'TestScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + + // Verify scheduled EventBridge Rule is created (no separate Scheduler needed) + template.hasResourceProperties('AWS::Events::Rule', { + Name: 'QS2-test-scheduler', + ScheduleExpression: 'rate(5 minutes)', + State: 'ENABLED', + Targets: [{ + Arn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + RedshiftDataParameters: { + Database: 'test-db', + DbUser: 'test-user', + Sql: 'SELECT * FROM test_table', + StatementName: 'QS2-test-scheduler', + WithEvent: true, + }, + }], + }); + + // Verify no EventBridge Scheduler is created + template.resourceCountIs('AWS::Scheduler::Schedule', 0); + }); + + test('creates scheduled rule with workgroup ARN and secretArn', () => { + // ARRANGE + const props = { + schedulerName: 'workgroup-scheduler', + database: 'test-db', + workgroupArn: 'arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup', + secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret-abc123', + sqls: ['SELECT 1', 'SELECT 2'], + schedule: scheduler.ScheduleExpression.rate(Duration.hours(1)), + }; + + // ACT + new RedshiftQueryScheduler(stack, 'WorkgroupScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Events::Rule', { + Name: 'QS2-workgroup-scheduler', + ScheduleExpression: 'rate(1 hour)', + Targets: [{ + Arn: 'arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup', + RedshiftDataParameters: { + Database: 'test-db', + SecretManagerArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret-abc123', + Sqls: ['SELECT 1', 'SELECT 2'], + StatementName: 'QS2-workgroup-scheduler', + WithEvent: true, + }, + }], + }); + }); + + test('maps single sql to array format for RedshiftQuery', () => { + // ARRANGE + const props = { + schedulerName: 'single-sql', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: 'SELECT COUNT(*) FROM orders', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(30)), + }; + + // ACT + new RedshiftQueryScheduler(stack, 'SingleSqlScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + + // Verify single SQL is converted to array format + template.hasResourceProperties('AWS::Events::Rule', { + Targets: [{ + RedshiftDataParameters: { + Sql: 'SELECT COUNT(*) FROM orders', // RedshiftQuery handles single SQL + }, + }], + }); + }); + + test('handles multiple sqls correctly', () => { + // ARRANGE + const props = { + schedulerName: 'multi-sql', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sqls: ['DELETE FROM temp_data', 'VACUUM temp_data', 'ANALYZE temp_data'], + schedule: scheduler.ScheduleExpression.rate(Duration.hours(2)), + }; + + // ACT + new RedshiftQueryScheduler(stack, 'MultiSqlScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + + // Verify multiple SQLs are passed as array + template.hasResourceProperties('AWS::Events::Rule', { + Targets: [{ + RedshiftDataParameters: { + Sqls: ['DELETE FROM temp_data', 'VACUUM temp_data', 'ANALYZE temp_data'], + }, + }], + }); + }); + }); + + describe('Validation', () => { + test('throws error when both clusterArn and workgroupArn are provided', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + workgroupArn: 'arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup', + dbUser: 'test-user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('Cannot specify both clusterArn and workgroupArn. Choose exactly one.'); + }); + + test('throws error when neither clusterArn nor workgroupArn are provided', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + dbUser: 'test-user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('Must specify exactly one of clusterArn or workgroupArn.'); + }); + + test('throws error when both dbUser and secretArn are provided', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret-abc123', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('Cannot specify both dbUser and secretArn. Choose exactly one.'); + }); + + test('throws error when dbUser is provided with workgroupArn', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + workgroupArn: 'arn:aws:redshift-serverless:us-east-1:123456789012:workgroup/test-workgroup', + dbUser: 'test-user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('Cannot specify dbUser when using workgroupArn.'); + }); + + test('throws error when both sql and sqls are provided', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: 'SELECT 1', + sqls: ['SELECT 2'], + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('Cannot specify both sql and sqls. Choose exactly one.'); + }); + + test('throws error when neither sql nor sqls are provided', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('Must specify exactly one of sql or sqls.'); + }); + + test('throws error when sqls array is empty', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sqls: [], + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('sqls array cannot be empty.'); + }); + + test('throws error when sql is empty string', () => { + // ARRANGE + const props = { + schedulerName: 'invalid-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: '', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT & ASSERT + expect(() => { + new RedshiftQueryScheduler(stack, 'InvalidScheduler', props); + }).toThrow('sql cannot be empty.'); + }); + }); + + describe('Edge cases', () => { + test('handles special characters in schedulerName', () => { + // ARRANGE + const props = { + schedulerName: 'test-scheduler_with-special.chars', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + }; + + // ACT + new RedshiftQueryScheduler(stack, 'SpecialCharsScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Events::Rule', { + Name: 'QS2-test-scheduler_with-special.chars', + Targets: [{ + RedshiftDataParameters: { + StatementName: 'QS2-test-scheduler_with-special.chars', + }, + }], + }); + }); + + test('creates scheduler with optional description and enabled flag', () => { + // ARRANGE + const props = { + schedulerName: 'described-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + description: 'Test scheduler description', + enabled: false, + }; + + // ACT + new RedshiftQueryScheduler(stack, 'DescribedScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Events::Rule', { + Description: 'Test scheduler description', + State: 'DISABLED', + }); + }); + + test('creates scheduler with custom IAM role', () => { + // ARRANGE + const customRole = new iam.Role(stack, 'CustomRedshiftRole', { + assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), + description: 'Custom role for Redshift query execution', + }); + customRole.addToPolicy(new iam.PolicyStatement({ + actions: ['redshift-data:ExecuteStatement'], + resources: ['*'], + })); + const props = { + schedulerName: 'role-scheduler', + database: 'test-db', + clusterArn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + dbUser: 'test-user', + sql: 'SELECT 1', + schedule: scheduler.ScheduleExpression.rate(Duration.minutes(5)), + role: customRole, + }; + + // ACT + new RedshiftQueryScheduler(stack, 'RoleScheduler', props); + + // ASSERT + const template = Template.fromStack(stack); + // Verify the custom role is used in the target + template.hasResourceProperties('AWS::Events::Rule', { + Name: 'QS2-role-scheduler', + Targets: [{ + Arn: 'arn:aws:redshift:us-east-1:123456789012:cluster:test-cluster', + RoleArn: { + 'Fn::GetAtt': ['CustomRedshiftRole1EB1A91A', 'Arn'], + }, + RedshiftDataParameters: { + Database: 'test-db', + DbUser: 'test-user', + Sql: 'SELECT 1', + StatementName: 'QS2-role-scheduler', + WithEvent: true, + }, + }], + }); + }); + }); +});