From f8df24ecce8505207750839d52261f6d81885264 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Mon, 5 Jul 2021 18:36:16 -0400 Subject: [PATCH 01/17] update glue max allocation units to 2 --- .../aws-kinesisstreams-gluejob/lib/index.ts | 4 +- .../test/test.kinesisstream-gluejob.test.ts | 3 +- .../core/lib/glue-database-defaults.ts | 4 +- .../core/lib/glue-database-helper.ts | 8 +-- .../core/lib/glue-job-defaults.ts | 41 ++++++++---- .../core/lib/glue-job-helper.ts | 64 ++++++++++--------- .../glue-job-helper.test.js.snap | 2 +- .../core/test/glue-job-helper.test.ts | 16 +++-- .../core/test/input-validation.test.ts | 26 ++++---- 9 files changed, 95 insertions(+), 73 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts index 5c53431ca..e0f503cdd 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts @@ -28,7 +28,7 @@ export interface KinesisstreamsToGluejobProps { * * @default - Default props are used */ - readonly kinesisStreamProps?: StreamProps | any; + readonly kinesisStreamProps?: StreamProps | any; /** * User provides props to override the default props for Glue ETL Jobs. Providing both this and * existingGlueJob will cause an error. @@ -41,7 +41,7 @@ export interface KinesisstreamsToGluejobProps { */ readonly glueJobProps?: glue.CfnJobProps | any; /** - * Existing GlueJob configuration. If this property is provided, any properties provided through @glueJobProps is ignored + * Existing GlueJob configuration. If this property is provided, any properties provided through @glueJobProps is ignored. */ readonly existingGlueJob?: glue.CfnJob; /** diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts index e1339a5b7..1aca41705 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts @@ -468,8 +468,7 @@ test('Do not pass fieldSchame or table (CfnTable), error out', () => { glueJobProps: { command: { name: 'glueetl', - pythonVersion: '3', - scriptPath: `s3://fakebucket/fakepath/fakefile.py` + pythonVersion: '3' } }, outputDataStore: { diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts index e88c96171..79df644c6 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts @@ -12,14 +12,14 @@ */ import { CfnDatabaseProps } from "@aws-cdk/aws-glue"; -import { Aws } from "@aws-cdk/core"; +import * as cdk from "@aws-cdk/core"; /** * Default database props */ export function DefaultGlueDatabaseProps(): CfnDatabaseProps { const _databaseProps: CfnDatabaseProps = { - catalogId: Aws.ACCOUNT_ID, + catalogId: cdk.Aws.ACCOUNT_ID, databaseInput: { description: 'An AWS Glue database generated by AWS Solutions Construct' } diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts index 9d4894154..b8335bbf6 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts @@ -11,8 +11,8 @@ * and limitations under the License. */ -import { CfnDatabase, CfnDatabaseProps } from "@aws-cdk/aws-glue"; -import { Construct } from "@aws-cdk/core"; +import * as glue from "@aws-cdk/aws-glue"; +import * as cdk from "@aws-cdk/core"; /** * Create an AWS Glue database with the properties provided @@ -20,6 +20,6 @@ import { Construct } from "@aws-cdk/core"; * @param scope * @param databaseProps */ -export function DefaultGlueDatabase(scope: Construct, databaseProps: CfnDatabaseProps): CfnDatabase { - return new CfnDatabase(scope, 'GlueDatabase', databaseProps); +export function DefaultGlueDatabase(scope: cdk.Construct, databaseProps: glue.CfnDatabaseProps): glue.CfnDatabase { + return new glue.CfnDatabase(scope, 'GlueDatabase', databaseProps); } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts index 611d7e0f2..1caf98dbf 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts @@ -11,20 +11,35 @@ * and limitations under the License. */ -import { CfnJob, CfnJobProps } from '@aws-cdk/aws-glue'; -import { IRole } from '@aws-cdk/aws-iam'; -import { IResolvable } from '@aws-cdk/core'; +import * as glue from '@aws-cdk/aws-glue'; +import * as iam from '@aws-cdk/aws-iam'; -export function DefaultGlueJobProps(jobRole: IRole, jobCommand: CfnJob.JobCommandProperty | IResolvable, - glueSecurityConfigName: string, _defaultArguments: {}, _glueVersion: string | undefined ): CfnJobProps | any { - const defaultGlueJobProps: CfnJobProps = { - command: jobCommand, - role: jobRole.roleArn, - securityConfiguration: glueSecurityConfigName, - defaultArguments: _defaultArguments, - // glue version though optional is required for streaming etl jobs otherwise it throws an error that 'command not found' - ...(_glueVersion !== undefined ? { glueVersion: _glueVersion } : { glueVersion: '2.0' }) - }; +export function DefaultGlueJobProps(jobRole: iam.IRole, userProvidedGlueJobProps: Partial, + glueSecurityConfigName: string, _defaultArguments: {} ): glue.CfnJobProps { + const _glueVersion: string | undefined = userProvidedGlueJobProps.glueVersion; + + let defaultGlueJobProps: glue.CfnJobProps; + + if (userProvidedGlueJobProps.workerType !== undefined && userProvidedGlueJobProps.numberOfWorkers !== undefined) { + defaultGlueJobProps = { + command: userProvidedGlueJobProps.command!, + role: jobRole.roleArn, + securityConfiguration: glueSecurityConfigName, + defaultArguments: _defaultArguments, + maxCapacity: 2, // setting default to 2 to ensure customers don't pay for something they don't need + // glue version though optional is required for streaming etl jobs otherwise it throws an error that 'command not found' + ...(_glueVersion !== undefined ? { glueVersion: _glueVersion } : { glueVersion: '2.0' }) + }; + } else { + defaultGlueJobProps = { + command: userProvidedGlueJobProps.command!, + role: jobRole.roleArn, + securityConfiguration: glueSecurityConfigName, + defaultArguments: _defaultArguments, + // glue version though optional is required for streaming etl jobs otherwise it throws an error that 'command not found' + ...(_glueVersion !== undefined ? { glueVersion: _glueVersion } : { glueVersion: '2.0' }) + }; + } return defaultGlueJobProps; } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts index 40e03e5e2..8be58fb5e 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts @@ -12,11 +12,11 @@ */ import * as glue from '@aws-cdk/aws-glue'; -import { Effect, IRole, Policy, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { Bucket, BucketProps, IBucket } from '@aws-cdk/aws-s3'; -import { Aws, Construct, IResolvable } from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; import * as defaults from '../'; -import { overrideProps } from './utils'; +// import { overrideProps } from './utils'; /** * Enumeration of data store types that could include S3, DynamoDB, DocumentDB, RDS or Redshift. Current @@ -47,19 +47,19 @@ export interface SinkDataStoreProps { * getResolvedOptions(sys.argv, ["JOB_NAME", "output_path", ]) * output_path = args["output_path"] */ - readonly existingS3OutputBucket?: Bucket + readonly existingS3OutputBucket?: s3.Bucket /** * If @existingS3OutputBUcket is provided, this parameter is ignored. If this parameter is not provided, * the construct will create a new bucket if the @datastoreType is S3. */ - readonly outputBucketProps?: BucketProps; + readonly outputBucketProps?: s3.BucketProps; } export interface BuildGlueJobProps { /** * Glue ETL job properties. */ - readonly glueJobProps?: glue.CfnJobProps | any + readonly glueJobProps?: glue.CfnJobProps | any /** * Existing instance of the S3 bucket object, if this is set then the script location is ignored. */ @@ -78,7 +78,7 @@ export interface BuildGlueJobProps { readonly outputDataStore?: SinkDataStoreProps } -export function buildGlueJob(scope: Construct, props: BuildGlueJobProps): [glue.CfnJob, IRole] { +export function buildGlueJob(scope: cdk.Construct, props: BuildGlueJobProps): [glue.CfnJob, iam.IRole] { if (!props.existingCfnJob) { if (props.glueJobProps) { return deployGlueJob(scope, props.glueJobProps, props.database!, props.table!, props.outputDataStore!); @@ -86,18 +86,18 @@ export function buildGlueJob(scope: Construct, props: BuildGlueJobProps): [glue. throw Error('Either glueJobProps or existingCfnJob is required'); } } else { - return [props.existingCfnJob, Role.fromRoleArn(scope, 'ExistingRole', props.existingCfnJob.role)]; + return [props.existingCfnJob, iam.Role.fromRoleArn(scope, 'ExistingRole', props.existingCfnJob.role)]; } } -export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, database: glue.CfnDatabase, table: glue.CfnTable, - outputDataStore: SinkDataStoreProps): [glue.CfnJob, IRole] { +export function deployGlueJob(scope: cdk.Construct, glueJobProps: Partial, database: glue.CfnDatabase, table: glue.CfnTable, + outputDataStore: SinkDataStoreProps): [glue.CfnJob, iam.IRole] { let _glueSecurityConfigName: string; if (glueJobProps.securityConfiguration === undefined) { _glueSecurityConfigName = 'ETLJobSecurityConfig'; - const _glueKMSKey = `arn:${Aws.PARTITION}:kms:${Aws.REGION}:${Aws.ACCOUNT_ID}:alias/aws/glue`; + const _glueKMSKey = `arn:${cdk.Aws.PARTITION}:kms:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:alias/aws/glue`; new glue.CfnSecurityConfiguration(scope, 'GlueSecurityConfig', { name: _glueSecurityConfigName, @@ -115,26 +115,26 @@ export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, _glueSecurityConfigName = glueJobProps.securityConfiguration; } - const _glueJobPolicy = new Policy(scope, 'LogPolicy', { + const _glueJobPolicy = new iam.Policy(scope, 'LogPolicy', { statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, actions: [ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents' ], - resources: [ `arn:${Aws.PARTITION}:logs:${Aws.REGION}:${Aws.ACCOUNT_ID}:log-group:/aws-glue/*` ] + resources: [ `arn:${cdk.Aws.PARTITION}:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws-glue/*` ] }) ] }); - let _jobRole: IRole; + let _jobRole: iam.IRole; if (glueJobProps.role) { - _jobRole = Role.fromRoleArn(scope, 'JobRole', glueJobProps.role); + _jobRole = iam.Role.fromRoleArn(scope, 'JobRole', glueJobProps.role); } else { _jobRole = defaults.createGlueJobRole(scope); } _glueJobPolicy.attachToRole(_jobRole); - let _outputLocation: [ Bucket, Bucket? ]; + let _outputLocation: [ s3.Bucket, s3.Bucket? ]; if (outputDataStore !== undefined && outputDataStore.datastoreType === SinkStoreType.S3) { if (outputDataStore.existingS3OutputBucket !== undefined) { _outputLocation = [ outputDataStore.existingS3OutputBucket, undefined ]; @@ -157,8 +157,10 @@ export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, ...glueJobProps.defaultArguments }; - const _newGlueJobProps: glue.CfnJobProps = overrideProps(defaults.DefaultGlueJobProps(_jobRole!, glueJobProps.command, - _glueSecurityConfigName, _jobArgumentsList, glueJobProps.glueVersion), glueJobProps); + const _defaultGlueJobProps = defaults.DefaultGlueJobProps(_jobRole!, glueJobProps, _glueSecurityConfigName, + _jobArgumentsList); + + const _newGlueJobProps: glue.CfnJobProps = defaults.overrideProps(_defaultGlueJobProps, glueJobProps); let _scriptLocation: string; if (isJobCommandProperty(_newGlueJobProps.command)) { @@ -169,7 +171,7 @@ export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, } } - const _scriptBucketLocation: IBucket = Bucket.fromBucketArn(scope, 'ScriptLocaiton', getS3ArnfromS3Url(_scriptLocation!)); + const _scriptBucketLocation: s3.IBucket = s3.Bucket.fromBucketArn(scope, 'ScriptLocaiton', getS3ArnfromS3Url(_scriptLocation!)); _scriptBucketLocation.grantRead(_jobRole); const _glueJob: glue.CfnJob = new glue.CfnJob(scope, 'KinesisETLJob', _newGlueJobProps); @@ -182,9 +184,9 @@ export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, * * @param scope - The AWS Construct under which the role is to be created */ -export function createGlueJobRole(scope: Construct): Role { - return new Role(scope, 'JobRole', { - assumedBy: new ServicePrincipal('glue.amazonaws.com'), +export function createGlueJobRole(scope: cdk.Construct): iam.Role { + return new iam.Role(scope, 'JobRole', { + assumedBy: new iam.ServicePrincipal('glue.amazonaws.com'), description: 'Service role that Glue custom ETL jobs will assume for exeuction', }); } @@ -192,7 +194,7 @@ export function createGlueJobRole(scope: Construct): Role { /** * This method creates an AWS Glue table. The method is called when an existing Glue table is not provided */ -export function createGlueTable(scope: Construct, database: glue.CfnDatabase, tableProps?: glue.CfnTableProps, +export function createGlueTable(scope: cdk.Construct, database: glue.CfnDatabase, tableProps?: glue.CfnTableProps, fieldSchema?: glue.CfnTable.ColumnProperty [], sourceType?: string, parameters?: any): glue.CfnTable { return defaults.DefaultGlueTable(scope, tableProps !== undefined ? tableProps : defaults.DefaultGlueTableProps(database, fieldSchema!, sourceType, parameters)); @@ -205,9 +207,9 @@ export function createGlueTable(scope: Construct, database: glue.CfnDatabase, ta * @param scope * @param databaseProps */ -export function createGlueDatabase(scope: Construct, databaseProps?: glue.CfnDatabaseProps): glue.CfnDatabase { - const _mergedDBProps: glue.CfnDatabaseProps = (databaseProps !== undefined) ? overrideProps(defaults.DefaultGlueDatabaseProps(), databaseProps) : - defaults.DefaultGlueDatabaseProps(); +export function createGlueDatabase(scope: cdk.Construct, databaseProps?: glue.CfnDatabaseProps): glue.CfnDatabase { + const _mergedDBProps: glue.CfnDatabaseProps = (databaseProps !== undefined) ? defaults.overrideProps( + defaults.DefaultGlueDatabaseProps(), databaseProps) : defaults.DefaultGlueDatabaseProps(); return defaults.DefaultGlueDatabase(scope, _mergedDBProps); } @@ -218,7 +220,7 @@ export function createGlueDatabase(scope: Construct, databaseProps?: glue.CfnDa */ function getS3ArnfromS3Url(s3Url: string): string { const splitString: string = s3Url.slice('s3://'.length); - return `arn:${Aws.PARTITION}:s3:::${splitString}`; + return `arn:${cdk.Aws.PARTITION}:s3:::${splitString}`; } /** @@ -226,7 +228,7 @@ function getS3ArnfromS3Url(s3Url: string): string { * * @param command */ -function isJobCommandProperty(command: glue.CfnJob.JobCommandProperty | IResolvable): command is glue.CfnJob.JobCommandProperty { +function isJobCommandProperty(command: glue.CfnJob.JobCommandProperty | cdk.IResolvable): command is glue.CfnJob.JobCommandProperty { if ((command as glue.CfnJob.JobCommandProperty).name || (command as glue.CfnJob.JobCommandProperty).pythonVersion || (command as glue.CfnJob.JobCommandProperty).scriptLocation) { diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap index 3e5f34224..ee2706351 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap @@ -826,7 +826,7 @@ Object { "Ref": "GlueTable", }, }, - "GlueVersion": "1.0", + "GlueVersion": "2.0", "Role": Object { "Fn::GetAtt": Array [ "CustomETLJobRole90A83A66", diff --git a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts index 83f320d64..a586ad634 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts @@ -32,11 +32,15 @@ test('Test deployment with role creation', () => { assumedBy: new ServicePrincipal('glue.amazonaws.com') }); - const cfnJobProps: CfnJobProps = defaults.DefaultGlueJobProps(_jobRole, { - name: _jobID, - pythonVersion: '3', - scriptLocation: 's3://fakelocation/script' - }, 'testETLJob', {}, '1.0'); + const _userProvidedCfnJobProps: Partial = { + command: { + name: _jobID, + pythonVersion: '3', + scriptLocation: 's3://fakelocation/script' + } + }; + + const cfnJobProps: CfnJobProps = defaults.DefaultGlueJobProps(_jobRole, _userProvidedCfnJobProps, 'testETLJob', {}); const _database = defaults.createGlueDatabase(stack, defaults.DefaultGlueDatabaseProps()); @@ -448,7 +452,7 @@ test('check for JobCommandProperty type', () => { defaults.buildGlueJob(stack, { glueJobProps: { command: { - fakekey: 'fakevalue' + name: 'fakevalue' } }, database: _database, diff --git a/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts b/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts index e7d69bfb7..9bddf4a53 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts @@ -12,19 +12,19 @@ */ // Imports -import * as defaults from '../'; -import { Stack } from '@aws-cdk/core'; -import { CreateScrapBucket } from './test-helper'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as sqs from '@aws-cdk/aws-sqs'; -import { MediaStoreContainerProps } from '../lib/mediastore-defaults'; -import * as mediastore from '@aws-cdk/aws-mediastore'; -import * as kinesis from '@aws-cdk/aws-kinesis'; -import * as sns from '@aws-cdk/aws-sns'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as glue from '@aws-cdk/aws-glue'; import * as iam from '@aws-cdk/aws-iam'; +import * as kinesis from '@aws-cdk/aws-kinesis'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as mediastore from '@aws-cdk/aws-mediastore'; +import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; +import { Stack } from '@aws-cdk/core'; +import * as defaults from '../'; +import { MediaStoreContainerProps } from '../lib/mediastore-defaults'; import { BuildSagemakerEndpoint } from '../lib/sagemaker-helper'; +import { CreateScrapBucket } from './test-helper'; test('Test with valid props', () => { const props: defaults.VerifiedProps = { @@ -218,9 +218,11 @@ test('Test fail Glue job check', () => { const jobProps: glue.CfnJobProps = defaults.DefaultGlueJobProps(_jobRole, { name: 'placeholder', - pythonVersion: '3', - scriptLocation: 's3://fakelocation/script' - }, 'testETLJob', {}, '1.0'); + command: { + pythonVersion: '3', + scriptLocation: 's3://fakelocation/script' + } + }, 'testETLJob', {}); const job = new glue.CfnJob(stack, 'placeholder', jobProps); From 1ab1c91a00b8aa96e7b6bd6b641d3cfb29a9dfc9 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Mon, 5 Jul 2021 18:44:52 -0400 Subject: [PATCH 02/17] update glue max allocation units to 2 --- .../aws-kinesisstreams-gluejob/lib/index.ts | 2 +- .../test/test.kinesisstream-gluejob.test.ts | 3 ++- .../core/test/glue-job-helper.test.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts index e0f503cdd..6bf37383f 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts @@ -28,7 +28,7 @@ export interface KinesisstreamsToGluejobProps { * * @default - Default props are used */ - readonly kinesisStreamProps?: StreamProps | any; + readonly kinesisStreamProps?: StreamProps | any; /** * User provides props to override the default props for Glue ETL Jobs. Providing both this and * existingGlueJob will cause an error. diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts index 1aca41705..e1339a5b7 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts @@ -468,7 +468,8 @@ test('Do not pass fieldSchame or table (CfnTable), error out', () => { glueJobProps: { command: { name: 'glueetl', - pythonVersion: '3' + pythonVersion: '3', + scriptPath: `s3://fakebucket/fakepath/fakefile.py` } }, outputDataStore: { diff --git a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts index a586ad634..e97f0e2ac 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts @@ -452,7 +452,7 @@ test('check for JobCommandProperty type', () => { defaults.buildGlueJob(stack, { glueJobProps: { command: { - name: 'fakevalue' + fakekey: 'fakevalue' } }, database: _database, From 3365c6e2494d7390d5e3b105e221aeb190114d60 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 14:53:24 -0400 Subject: [PATCH 03/17] resolving workerType and maxCapacity config related rules --- .../aws-kinesisstreams-gluejob/lib/index.ts | 9 +- .../test.kinesisstream-gluejob.test.js.snap | 22 +- .../test/integ.existing-job.expected.json | 2 +- .../test/integ.existing-job.ts | 3 +- .../test/integ.no-arguments.expected.json | 6 +- .../test/test.kinesisstream-gluejob.test.ts | 2 +- .../core/lib/glue-job-defaults.ts | 45 +- .../core/lib/glue-job-helper.ts | 80 +-- .../glue-job-helper.test.js.snap | 472 +++++++++++++++++- .../core/test/glue-job-helper.test.ts | 177 ++++++- .../core/test/input-validation.test.ts | 11 +- 11 files changed, 729 insertions(+), 100 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts index 6bf37383f..c82a8e3f6 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts @@ -37,11 +37,14 @@ export interface KinesisstreamsToGluejobProps { * for CfnJobProps. If a role is not passed, the construct creates one for you and attaches the appropriate * role policies * + * The default props would set the Glue Version 2.0, with 2 Workers and WorkerType as G1.X. For details on + * defining job, please refer the following link for documentation - https://docs.aws.amazon.com/glue/latest/webapi/API_Job.html + * * @default - None */ readonly glueJobProps?: glue.CfnJobProps | any; /** - * Existing GlueJob configuration. If this property is provided, any properties provided through @glueJobProps is ignored. + * Existing GlueJob configuration. If this property is provided, any properties provided through @glueJobProps is ignored */ readonly existingGlueJob?: glue.CfnJob; /** @@ -211,8 +214,8 @@ export class KinesisstreamsToGluejob extends Construct { rules_to_suppress: [{ id: 'W12', reason: 'Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration.\ - CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to\ - publish metrics only for AWS Glue' + CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to\ + publish metrics only for AWS Glue' }] } }; diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap index ecdb27e90..b6289e72a 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap @@ -92,7 +92,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -417,6 +417,7 @@ Object { }, }, "GlueVersion": "2.0", + "NumberOfWorkers": 2, "Role": Object { "Fn::GetAtt": Array [ "testkinesisstreamslambdaJobRole42199B9C", @@ -424,6 +425,7 @@ Object { ], }, "SecurityConfiguration": "ETLJobSecurityConfig", + "WorkerType": "G.1X", }, "Type": "AWS::Glue::Job", }, @@ -768,7 +770,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -1056,7 +1058,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -1381,6 +1383,7 @@ Object { }, }, "GlueVersion": "2.0", + "NumberOfWorkers": 2, "Role": Object { "Fn::GetAtt": Array [ "testkinesisstreamslambdaJobRole42199B9C", @@ -1388,6 +1391,7 @@ Object { ], }, "SecurityConfiguration": "ETLJobSecurityConfig", + "WorkerType": "G.1X", }, "Type": "AWS::Glue::Job", }, @@ -1697,7 +1701,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -2022,6 +2026,7 @@ Object { }, }, "GlueVersion": "2.0", + "NumberOfWorkers": 2, "Role": Object { "Fn::GetAtt": Array [ "testkinesisstreamslambdaJobRole42199B9C", @@ -2029,6 +2034,7 @@ Object { ], }, "SecurityConfiguration": "ETLJobSecurityConfig", + "WorkerType": "G.1X", }, "Type": "AWS::Glue::Job", }, @@ -2330,7 +2336,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -2655,6 +2661,7 @@ Object { }, }, "GlueVersion": "2.0", + "NumberOfWorkers": 2, "Role": Object { "Fn::GetAtt": Array [ "testkinesisstreamslambdaJobRole42199B9C", @@ -2662,6 +2669,7 @@ Object { ], }, "SecurityConfiguration": "ETLJobSecurityConfig", + "WorkerType": "G.1X", }, "Type": "AWS::Glue::Job", }, @@ -2979,7 +2987,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -3304,6 +3312,7 @@ Object { }, }, "GlueVersion": "2.0", + "NumberOfWorkers": 2, "Role": Object { "Fn::GetAtt": Array [ "existingStreamJobJobRoleA1ADDA0A", @@ -3311,6 +3320,7 @@ Object { ], }, "SecurityConfiguration": "ETLJobSecurityConfig", + "WorkerType": "G.1X", }, "Type": "AWS::Glue::Job", }, diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json index 348a52e78..eca59b5e9 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json @@ -347,7 +347,7 @@ "rules_to_suppress": [ { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" } ] } diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.ts index e8509e9a6..82ae33618 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.ts @@ -16,9 +16,8 @@ import { CfnJob } from '@aws-cdk/aws-glue'; import { Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { Bucket, CfnBucket } from '@aws-cdk/aws-s3'; import { App, Duration, Stack } from '@aws-cdk/core'; -import { SinkStoreType } from '@aws-solutions-constructs/core'; +import { generateIntegStackName, SinkStoreType } from '@aws-solutions-constructs/core'; import { KinesisstreamsToGluejob } from '../lib'; -import { generateIntegStackName } from '@aws-solutions-constructs/core'; // Setup const app = new App(); diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json index fef702c29..643fd2cca 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json @@ -497,7 +497,9 @@ } }, "GlueVersion": "2.0", - "SecurityConfiguration": "ETLJobSecurityConfig" + "NumberOfWorkers": 2, + "SecurityConfiguration": "ETLJobSecurityConfig", + "WorkerType": "G.1X" } }, "GlueDatabase": { @@ -741,7 +743,7 @@ "rules_to_suppress": [ { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" } ] } diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts index e1339a5b7..dd2eb5b84 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts @@ -249,7 +249,7 @@ test('Pattern minimal deployment', () => { rules_to_suppress: [ { id: "W12", - reason: "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" + reason: "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" } ] } diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts index 1caf98dbf..492ab85b0 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-defaults.ts @@ -11,35 +11,28 @@ * and limitations under the License. */ -import * as glue from '@aws-cdk/aws-glue'; -import * as iam from '@aws-cdk/aws-iam'; +import { CfnJobProps } from '@aws-cdk/aws-glue'; +import { IRole } from '@aws-cdk/aws-iam'; -export function DefaultGlueJobProps(jobRole: iam.IRole, userProvidedGlueJobProps: Partial, - glueSecurityConfigName: string, _defaultArguments: {} ): glue.CfnJobProps { - const _glueVersion: string | undefined = userProvidedGlueJobProps.glueVersion; +export function DefaultGlueJobProps(jobRole: IRole, userProvidedGlueJobProps: CfnJobProps, + glueSecurityConfigName: string, defaultArguments: {}): CfnJobProps { + const glueVersion: string | undefined = userProvidedGlueJobProps.glueVersion; - let defaultGlueJobProps: glue.CfnJobProps; + // setting default to 2 to reduce cost + const maxCapacity = glueVersion === "1.0" && !(userProvidedGlueJobProps.workerType + || userProvidedGlueJobProps.numberOfWorkers) ? 2 : undefined; - if (userProvidedGlueJobProps.workerType !== undefined && userProvidedGlueJobProps.numberOfWorkers !== undefined) { - defaultGlueJobProps = { - command: userProvidedGlueJobProps.command!, - role: jobRole.roleArn, - securityConfiguration: glueSecurityConfigName, - defaultArguments: _defaultArguments, - maxCapacity: 2, // setting default to 2 to ensure customers don't pay for something they don't need - // glue version though optional is required for streaming etl jobs otherwise it throws an error that 'command not found' - ...(_glueVersion !== undefined ? { glueVersion: _glueVersion } : { glueVersion: '2.0' }) - }; - } else { - defaultGlueJobProps = { - command: userProvidedGlueJobProps.command!, - role: jobRole.roleArn, - securityConfiguration: glueSecurityConfigName, - defaultArguments: _defaultArguments, - // glue version though optional is required for streaming etl jobs otherwise it throws an error that 'command not found' - ...(_glueVersion !== undefined ? { glueVersion: _glueVersion } : { glueVersion: '2.0' }) - }; - } + const defaultGlueJobProps: CfnJobProps = { + command: userProvidedGlueJobProps.command!, + role: jobRole.roleArn, + securityConfiguration: glueSecurityConfigName, + defaultArguments, + maxCapacity, + numberOfWorkers: (!glueVersion || glueVersion === "2.0") ? 2 : undefined, // defaulting to 2 workers, + workerType: (!glueVersion || glueVersion === "2.0") ? 'G.1X' : undefined, // defaulting to G.1X as it is preferred with glue version 2.0 + // glue version though optional is required for streaming etl jobs otherwise it throws an error that 'command not found' + glueVersion: glueVersion ? glueVersion : '2.0' + }; return defaultGlueJobProps; } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts index 8be58fb5e..803fefe40 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts @@ -12,11 +12,11 @@ */ import * as glue from '@aws-cdk/aws-glue'; -import * as iam from '@aws-cdk/aws-iam'; -import * as s3 from '@aws-cdk/aws-s3'; -import * as cdk from '@aws-cdk/core'; +import { Effect, IRole, Policy, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { Bucket, BucketProps, IBucket } from '@aws-cdk/aws-s3'; +import { Aws, Construct, IResolvable } from '@aws-cdk/core'; import * as defaults from '../'; -// import { overrideProps } from './utils'; +import { overrideProps } from './utils'; /** * Enumeration of data store types that could include S3, DynamoDB, DocumentDB, RDS or Redshift. Current @@ -47,19 +47,19 @@ export interface SinkDataStoreProps { * getResolvedOptions(sys.argv, ["JOB_NAME", "output_path", ]) * output_path = args["output_path"] */ - readonly existingS3OutputBucket?: s3.Bucket + readonly existingS3OutputBucket?: Bucket /** * If @existingS3OutputBUcket is provided, this parameter is ignored. If this parameter is not provided, * the construct will create a new bucket if the @datastoreType is S3. */ - readonly outputBucketProps?: s3.BucketProps; + readonly outputBucketProps?: BucketProps; } export interface BuildGlueJobProps { /** * Glue ETL job properties. */ - readonly glueJobProps?: glue.CfnJobProps | any + readonly glueJobProps?: glue.CfnJobProps | any /** * Existing instance of the S3 bucket object, if this is set then the script location is ignored. */ @@ -78,26 +78,36 @@ export interface BuildGlueJobProps { readonly outputDataStore?: SinkDataStoreProps } -export function buildGlueJob(scope: cdk.Construct, props: BuildGlueJobProps): [glue.CfnJob, iam.IRole] { +export function buildGlueJob(scope: Construct, props: BuildGlueJobProps): [glue.CfnJob, IRole] { if (!props.existingCfnJob) { if (props.glueJobProps) { + if (props.glueJobProps.glueVersion === '2.0' && props.glueJobProps.maxCapacity) { + throw Error('Cannot set "MaxCapacity" with GlueVersion 2.0 or higher. Use "NumberOfWorkers" and "WorkerType". ' + + 'Refer the API documentation https://docs.aws.amazon.com/glue/latest/webapi/API_Job.html for more details'); + } + + if (props.glueJobProps.maxCapacity && (props.glueJobProps.numberOfWorkers || props.glueJobProps.workerType)) { + throw Error('Cannot set MaxCapacity and "WorkerType" or "NumberOfWorkers". If using glueVersion 2.0 or beyond, ' + + 'it is recommended to use "WorkerType" or "NumberOfWorkers"'); + } + return deployGlueJob(scope, props.glueJobProps, props.database!, props.table!, props.outputDataStore!); } else { throw Error('Either glueJobProps or existingCfnJob is required'); } } else { - return [props.existingCfnJob, iam.Role.fromRoleArn(scope, 'ExistingRole', props.existingCfnJob.role)]; + return [props.existingCfnJob, Role.fromRoleArn(scope, 'ExistingRole', props.existingCfnJob.role)]; } } -export function deployGlueJob(scope: cdk.Construct, glueJobProps: Partial, database: glue.CfnDatabase, table: glue.CfnTable, - outputDataStore: SinkDataStoreProps): [glue.CfnJob, iam.IRole] { +export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, database: glue.CfnDatabase, table: glue.CfnTable, + outputDataStore: SinkDataStoreProps): [glue.CfnJob, IRole] { let _glueSecurityConfigName: string; if (glueJobProps.securityConfiguration === undefined) { _glueSecurityConfigName = 'ETLJobSecurityConfig'; - const _glueKMSKey = `arn:${cdk.Aws.PARTITION}:kms:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:alias/aws/glue`; + const _glueKMSKey = `arn:${Aws.PARTITION}:kms:${Aws.REGION}:${Aws.ACCOUNT_ID}:alias/aws/glue`; new glue.CfnSecurityConfiguration(scope, 'GlueSecurityConfig', { name: _glueSecurityConfigName, @@ -115,26 +125,26 @@ export function deployGlueJob(scope: cdk.Construct, glueJobProps: Partial { // Stack const stack = new Stack(); - const _jobID = 'glueetl'; const _jobRole = new Role(stack, 'CustomETLJobRole', { assumedBy: new ServicePrincipal('glue.amazonaws.com') }); - const _userProvidedCfnJobProps: Partial = { + const cfnJobProps: CfnJobProps = defaults.DefaultGlueJobProps(_jobRole, { command: { - name: _jobID, + name: 'glueetl', pythonVersion: '3', - scriptLocation: 's3://fakelocation/script' - } - }; - - const cfnJobProps: CfnJobProps = defaults.DefaultGlueJobProps(_jobRole, _userProvidedCfnJobProps, 'testETLJob', {}); + scriptLocation: new Bucket(stack, 'ScriptBucket').bucketArn, + }, + role: _jobRole.roleArn + }, 'testETLJob', {}); const _database = defaults.createGlueDatabase(stack, defaults.DefaultGlueDatabaseProps()); @@ -62,7 +60,12 @@ test('Test deployment with role creation', () => { Command: { Name: "glueetl", PythonVersion: "3", - ScriptLocation: "s3://fakelocation/script" + ScriptLocation: { + "Fn::GetAtt": [ + "ScriptBucketAAD020DD", + "Arn" + ] + } }, Role: { "Fn::GetAtt": [ @@ -70,7 +73,10 @@ test('Test deployment with role creation', () => { "Arn" ] }, - SecurityConfiguration: "testETLJob" + GlueVersion: "2.0", + NumberOfWorkers: 2, + SecurityConfiguration: "testETLJob", + WorkerType: "G.1X" } }, ResourcePart.CompleteDefinition); }); @@ -155,8 +161,6 @@ test('Test custom deployment properties', () => { description: 'Existing role' }).roleArn, glueVersion: '1', - allocatedCapacity: 2, - maxCapacity: 4, numberOfWorkers: 2, workerType: 'Standard' }; @@ -181,14 +185,12 @@ test('Test custom deployment properties', () => { // check if Glue Job Resource was created correctly expect(stack).toHaveResourceLike('AWS::Glue::Job', { Properties: { - AllocatedCapacity: 2, Command: { Name: "glueetl", PythonVersion: "3", ScriptLocation: "s3://existingFakeLocation/existingScript", }, GlueVersion: "1", - MaxCapacity: 4, NumberOfWorkers: 2, Role: { "Fn::GetAtt": [ @@ -465,4 +467,151 @@ test('check for JobCommandProperty type', () => { } catch (error) { expect(error).toBeInstanceOf(Error); } +}); + +// -------------------------------------------------------------- +// Supply maxCapacity with GlueVersion 2.0 and error out +// -------------------------------------------------------------- +test('GlueJob configuration with glueVersion 2.0 should not support maxCapacity and error out', () => { + const stack = new Stack(); + try { + const _database = defaults.createGlueDatabase(stack); + defaults.buildGlueJob(stack, { + outputDataStore: { + datastoreType: defaults.SinkStoreType.S3 + }, + database: _database, + table: defaults.createGlueTable(stack, _database, defaults.DefaultGlueTableProps(_database, [{ + name: "id", + type: "int", + comment: "" + }], 'kinesis', {STREAM_NAME: 'testStream'})), + glueJobProps: { + glueVersion: '2.0', + maxCapacity: '2' + } + }); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } +}); + +// -------------------------------------------------------------- +// Supply maxCapacity with GlueVersion 1.0 +// -------------------------------------------------------------- +test('GlueJob configuration with glueVersion 1.0 should support maxCapacity', () => { + const stack = new Stack(); + const _database = defaults.createGlueDatabase(stack); + defaults.buildGlueJob(stack, { + outputDataStore: { + datastoreType: defaults.SinkStoreType.S3 + }, + database: _database, + table: defaults.createGlueTable(stack, _database, defaults.DefaultGlueTableProps(_database, [{ + name: "id", + type: "int", + comment: "" + }], 'kinesis', {STREAM_NAME: 'testStream'})), + glueJobProps: { + command: { + name: "gluejob1.0", + pythonVersion: '3', + scriptLocation: 's3://fakelocation/script' + }, + glueVersion: '1.0', + maxCapacity: 2 + } + }); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Fail if setting maxCapacity and WorkerType/ NumberOfWorkers +// -------------------------------------------------------------- +test('Cannot use maxCapacity and WorkerType, so error out', () => { + const stack = new Stack(); + try { + const _database = defaults.createGlueDatabase(stack); + defaults.buildGlueJob(stack, { + outputDataStore: { + datastoreType: defaults.SinkStoreType.S3 + }, + database: _database, + table: defaults.createGlueTable(stack, _database, defaults.DefaultGlueTableProps(_database, [{ + name: "id", + type: "int", + comment: "" + }], 'kinesis', {STREAM_NAME: 'testStream'})), + glueJobProps: { + command: { + name: "gluejob1.0", + pythonVersion: '3' + }, + glueVersion: '1.0', + maxCapacity: '2', + workerType: 'Standard' + } + }); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } +}); + +test('Cannot use maxCapacity and WorkerType, so error out', () => { + const stack = new Stack(); + try { + const _database = defaults.createGlueDatabase(stack); + defaults.buildGlueJob(stack, { + outputDataStore: { + datastoreType: defaults.SinkStoreType.S3 + }, + database: _database, + table: defaults.createGlueTable(stack, _database, defaults.DefaultGlueTableProps(_database, [{ + name: "id", + type: "int", + comment: "" + }], 'kinesis', {STREAM_NAME: 'testStream'})), + glueJobProps: { + command: { + name: "gluejob1.0", + pythonVersion: '3' + }, + glueVersion: '1.0', + maxCapacity: '2', + numberOfWorkers: 2 + } + }); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } +}); + +test('Cannot use maxCapacity and WorkerType, so error out', () => { + const stack = new Stack(); + try { + const _database = defaults.createGlueDatabase(stack); + defaults.buildGlueJob(stack, { + outputDataStore: { + datastoreType: defaults.SinkStoreType.S3 + }, + database: _database, + table: defaults.createGlueTable(stack, _database, defaults.DefaultGlueTableProps(_database, [{ + name: "id", + type: "int", + comment: "" + }], 'kinesis', {STREAM_NAME: 'testStream'})), + glueJobProps: { + command: { + name: "gluejob1.0", + pythonVersion: '3' + }, + glueVersion: '1.0', + maxCapacity: '2', + numberOfWorkers: 2, + workerType: 'G1.X' + } + }); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts b/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts index 9bddf4a53..3df3f4d15 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/input-validation.test.ts @@ -18,6 +18,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; import * as lambda from '@aws-cdk/aws-lambda'; import * as mediastore from '@aws-cdk/aws-mediastore'; +import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; import { Stack } from '@aws-cdk/core'; @@ -217,12 +218,14 @@ test('Test fail Glue job check', () => { }); const jobProps: glue.CfnJobProps = defaults.DefaultGlueJobProps(_jobRole, { - name: 'placeholder', command: { + name: 'glueetl', pythonVersion: '3', - scriptLocation: 's3://fakelocation/script' - } - }, 'testETLJob', {}); + scriptLocation: new s3.Bucket(stack, 'ScriptBucket').bucketArn, + }, + role: new iam.Role(stack, 'JobRole', { + assumedBy: new iam.ServicePrincipal('glue.amazonaws.com') + }).roleArn}, 'testETLJob', {}); const job = new glue.CfnJob(stack, 'placeholder', jobProps); From aee778f9122400e191fb6d3f4258086f70805223 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:06:22 -0400 Subject: [PATCH 04/17] resolving workerType and maxCapacity config related rules --- .../aws-kinesisstreams-gluejob/lib/index.ts | 4 ++-- .../test.kinesisstream-gluejob.test.js.snap | 12 ++++++------ .../test/test.kinesisstream-gluejob.test.ts | 2 +- .../core/lib/glue-database-helper.ts | 10 ++++++---- .../core/lib/glue-job-helper.ts | 6 +++--- .../test/__snapshots__/glue-job-helper.test.js.snap | 4 ++-- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts index c82a8e3f6..791eacf4b 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts @@ -214,8 +214,8 @@ export class KinesisstreamsToGluejob extends Construct { rules_to_suppress: [{ id: 'W12', reason: 'Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration.\ - CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to\ - publish metrics only for AWS Glue' + CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to\ + publish metrics only for AWS Glue' }] } }; diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap index b6289e72a..ee0857d0f 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/__snapshots__/test.kinesisstream-gluejob.test.js.snap @@ -92,7 +92,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -770,7 +770,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -1058,7 +1058,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -1701,7 +1701,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -2336,7 +2336,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, @@ -2987,7 +2987,7 @@ Object { "rules_to_suppress": Array [ Object { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue", }, ], }, diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts index dd2eb5b84..e1339a5b7 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/test.kinesisstream-gluejob.test.ts @@ -249,7 +249,7 @@ test('Pattern minimal deployment', () => { rules_to_suppress: [ { id: "W12", - reason: "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" + reason: "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" } ] } diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts index b8335bbf6..2efe29c43 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts @@ -11,8 +11,10 @@ * and limitations under the License. */ -import * as glue from "@aws-cdk/aws-glue"; -import * as cdk from "@aws-cdk/core"; +import { CfnDatabase, CfnDatabaseProps } from "@aws-cdk/aws-glue"; +import { Construct } from "@aws-cdk/core"; + + /** * Create an AWS Glue database with the properties provided @@ -20,6 +22,6 @@ import * as cdk from "@aws-cdk/core"; * @param scope * @param databaseProps */ -export function DefaultGlueDatabase(scope: cdk.Construct, databaseProps: glue.CfnDatabaseProps): glue.CfnDatabase { - return new glue.CfnDatabase(scope, 'GlueDatabase', databaseProps); +export function DefaultGlueDatabase(scope: Construct, databaseProps: CfnDatabaseProps): CfnDatabase { + return new CfnDatabase(scope, 'GlueDatabase', databaseProps); } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts index 803fefe40..51bc469ca 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts @@ -163,7 +163,7 @@ export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, "--database_name": database.ref, "--table_name": table.ref, ...((outputDataStore === undefined || (outputDataStore && outputDataStore.datastoreType === SinkStoreType.S3)) && - { '--output_path' : `s3a://${_outputLocation[0].bucketName}/output/` }), + { '--output_path' : `s3a://${_outputLocation[0].bucketName}/output/` }), ...glueJobProps.defaultArguments }; @@ -238,8 +238,8 @@ function getS3ArnfromS3Url(s3Url: string): string { */ function isJobCommandProperty(command: glue.CfnJob.JobCommandProperty | IResolvable): command is glue.CfnJob.JobCommandProperty { if ((command as glue.CfnJob.JobCommandProperty).name || - (command as glue.CfnJob.JobCommandProperty).pythonVersion || - (command as glue.CfnJob.JobCommandProperty).scriptLocation) { + (command as glue.CfnJob.JobCommandProperty).pythonVersion || + (command as glue.CfnJob.JobCommandProperty).scriptLocation) { return true; } else { defaults.printWarning('command not of type JobCommandProperty type'); diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap index 669724076..79f77a002 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap @@ -1190,7 +1190,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.81]}", + ":s3:::en[TOKEN.82]}", ], ], }, @@ -1202,7 +1202,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.81]}/*", + ":s3:::en[TOKEN.82]}/*", ], ], }, From 6ac636d427c608e74466c2afdd4f1a15a31ca6d0 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:07:45 -0400 Subject: [PATCH 05/17] resolving workerType and maxCapacity config related rules --- .../core/lib/glue-database-defaults.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts index 79df644c6..e88c96171 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-defaults.ts @@ -12,14 +12,14 @@ */ import { CfnDatabaseProps } from "@aws-cdk/aws-glue"; -import * as cdk from "@aws-cdk/core"; +import { Aws } from "@aws-cdk/core"; /** * Default database props */ export function DefaultGlueDatabaseProps(): CfnDatabaseProps { const _databaseProps: CfnDatabaseProps = { - catalogId: cdk.Aws.ACCOUNT_ID, + catalogId: Aws.ACCOUNT_ID, databaseInput: { description: 'An AWS Glue database generated by AWS Solutions Construct' } From 7cb2b02b1ab29e88ed03f1973a44fb39d74f2040 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:20:10 -0400 Subject: [PATCH 06/17] removing additional lines from the file --- .../@aws-solutions-constructs/core/lib/glue-database-helper.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts index 2efe29c43..9d4894154 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-database-helper.ts @@ -14,8 +14,6 @@ import { CfnDatabase, CfnDatabaseProps } from "@aws-cdk/aws-glue"; import { Construct } from "@aws-cdk/core"; - - /** * Create an AWS Glue database with the properties provided * From 0ed148dfc5f64421cc3606717e29da18666b1c53 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:26:56 -0400 Subject: [PATCH 07/17] fix snapshots for glue helper --- .../core/test/__snapshots__/glue-job-helper.test.js.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap index 79f77a002..744200b37 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap @@ -1190,7 +1190,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.82]}", + ":s3:::en[TOKEN.83]}", ], ], }, @@ -1202,7 +1202,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.82]}/*", + ":s3:::en[TOKEN.83]}/*", ], ], }, From 4342f7728754463f66839387bc3deade4ebd75e6 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:33:29 -0400 Subject: [PATCH 08/17] fix snapshots for glue helper --- .../test/integ.existing-job.expected.json | 2 +- .../test/integ.no-arguments.expected.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json index eca59b5e9..348a52e78 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.existing-job.expected.json @@ -347,7 +347,7 @@ "rules_to_suppress": [ { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" } ] } diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json index 643fd2cca..2d9b32c0c 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/test/integ.no-arguments.expected.json @@ -743,7 +743,7 @@ "rules_to_suppress": [ { "id": "W12", - "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" + "reason": "Glue Security Configuration does not have an ARN, and the policy only allows reading the configuration. CloudWatch metrics also do not have an ARN but adding a namespace condition to the policy to allow it to publish metrics only for AWS Glue" } ] } From 55d17204d22dcbfb9b963017265df75f94c88e77 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:39:06 -0400 Subject: [PATCH 09/17] fix snapshots for glue helper --- .../core/test/__snapshots__/glue-job-helper.test.js.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap index 744200b37..79f77a002 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap @@ -1190,7 +1190,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.83]}", + ":s3:::en[TOKEN.82]}", ], ], }, @@ -1202,7 +1202,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.83]}/*", + ":s3:::en[TOKEN.82]}/*", ], ], }, From 804cf0c4982f46cefbf3f7d439aa394b85b71cc2 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:46:57 -0400 Subject: [PATCH 10/17] fix snapshots for glue helper --- .../@aws-solutions-constructs/core/test/glue-job-helper.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts index 3c5516ab5..75e39589b 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts @@ -35,7 +35,7 @@ test('Test deployment with role creation', () => { command: { name: 'glueetl', pythonVersion: '3', - scriptLocation: new Bucket(stack, 'ScriptBucket').bucketArn, + scriptLocation: 's3://fakescriptlocation/fakebucket', }, role: _jobRole.roleArn }, 'testETLJob', {}); From 91a17ba575a589bc8af347e28ec034afc68dc07f Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 15:52:20 -0400 Subject: [PATCH 11/17] fix snapshots for glue helper --- .../core/test/__snapshots__/glue-job-helper.test.js.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap index 79f77a002..166dfecf7 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap @@ -1190,7 +1190,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.82]}", + ":s3:::en[TOKEN.78]}", ], ], }, @@ -1202,7 +1202,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.82]}/*", + ":s3:::en[TOKEN.78]}/*", ], ], }, From f5a9a21622034bb13e01dd476c22cb18733120f9 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 16:04:10 -0400 Subject: [PATCH 12/17] fix snapshots for glue helper --- .../__snapshots__/glue-job-helper.test.js.snap | 16 +++------------- .../core/test/glue-job-helper.test.ts | 7 +------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap index 166dfecf7..459d690a4 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/glue-job-helper.test.js.snap @@ -1190,7 +1190,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.78]}", + ":s3:::fakescriptlocation/fakebucket", ], ], }, @@ -1202,7 +1202,7 @@ Object { Object { "Ref": "AWS::Partition", }, - ":s3:::en[TOKEN.78]}/*", + ":s3:::fakescriptlocation/fakebucket/*", ], ], }, @@ -1248,12 +1248,7 @@ Object { "Command": Object { "Name": "glueetl", "PythonVersion": "3", - "ScriptLocation": Object { - "Fn::GetAtt": Array [ - "ScriptBucketAAD020DD", - "Arn", - ], - }, + "ScriptLocation": "s3://fakescriptlocation/fakebucket", }, "DefaultArguments": Object { "--database_name": Object { @@ -1512,11 +1507,6 @@ Object { }, "Type": "AWS::S3::BucketPolicy", }, - "ScriptBucketAAD020DD": Object { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, }, } `; diff --git a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts index 75e39589b..e34ecc327 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts @@ -60,12 +60,7 @@ test('Test deployment with role creation', () => { Command: { Name: "glueetl", PythonVersion: "3", - ScriptLocation: { - "Fn::GetAtt": [ - "ScriptBucketAAD020DD", - "Arn" - ] - } + ScriptLocation: "s3://fakescriptlocation/fakebucket" }, Role: { "Fn::GetAtt": [ From 1328b6705534cc96bb391198c651ed3ab9529a9f Mon Sep 17 00:00:00 2001 From: nihitkas Date: Wed, 7 Jul 2021 16:31:19 -0400 Subject: [PATCH 13/17] documentation rephrase --- .../aws-kinesisstreams-gluejob/lib/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts index 791eacf4b..1db3396f8 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts @@ -37,8 +37,8 @@ export interface KinesisstreamsToGluejobProps { * for CfnJobProps. If a role is not passed, the construct creates one for you and attaches the appropriate * role policies * - * The default props would set the Glue Version 2.0, with 2 Workers and WorkerType as G1.X. For details on - * defining job, please refer the following link for documentation - https://docs.aws.amazon.com/glue/latest/webapi/API_Job.html + * The default props will set the Glue Version 2.0, with 2 Workers and WorkerType as G1.X. For details on + * defining a Glue Job, please refer the following link for documentation - https://docs.aws.amazon.com/glue/latest/webapi/API_Job.html * * @default - None */ From 04ebe40704503fc2ad975a4a1037bd80731ba66a Mon Sep 17 00:00:00 2001 From: nihitkas Date: Tue, 27 Jul 2021 19:34:59 -0400 Subject: [PATCH 14/17] new construct first commit with README and architecture --- .../aws-stepfunctionstask-sqs/.eslintignore | 4 + .../aws-stepfunctionstask-sqs/.gitignore | 15 +++ .../aws-stepfunctionstask-sqs/.npmignore | 21 ++++ .../aws-stepfunctionstask-sqs/README.md | 94 ++++++++++++++++ .../architecture.png | Bin 0 -> 87896 bytes .../aws-stepfunctionstask-sqs/lib/index.ts | 12 +++ .../aws-stepfunctionstask-sqs/package.json | 100 ++++++++++++++++++ 7 files changed, 246 insertions(+) create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/architecture.png create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore new file mode 100644 index 000000000..910cb0513 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore @@ -0,0 +1,4 @@ +lib/*.js +test/*.js +*.d.ts +coverage \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore @@ -0,0 +1,21 @@ +# Exclude typescript source and config +*.ts +tsconfig.json +coverage +.nyc_output +*.tgz +*.snk +*.tsbuildinfo + +# Include javascript files and typescript declarations +!*.js +!*.d.ts + +# Exclude jsii outdir +dist + +# Include .jsii +!.jsii + +# Include .jsii +!.jsii \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md new file mode 100644 index 000000000..ba80550bd --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md @@ -0,0 +1,94 @@ +# aws-stepfunctionstask-sqs module + + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> All classes are under active development and subject to non-backward compatible changes or removal in any +> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. +> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + + +| **Reference Documentation**: | https://docs.aws.amazon.com/solutions/latest/constructs/ | +| :--------------------------- | :------------------------------------------------------------------------------------------------ | + +
+ +| **Language** | **Package** | +| :--------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| ![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python | `aws_solutions_constructs.aws_stepfunctionstask_sqs` | +| ![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript | `@aws-solutions-constructs/aws-stepfunctionstask-sqs` | +| ![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java | `software.amazon.awsconstructs.services.stepfunctionstasksqs` | + +This AWS Solutions Construct implements an Amazon SQS queue connected to an AWS Lambda function. + +Here is a minimal deployable pattern definition in Typescipr: + +```typescript +TBD; +``` + +## Initializer + +```text +new StepfunctionstaskSqs(scope: Construct, id: string, props: StepfunctionstaskToSqsProps); +``` + +_Parameters_ + +- scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +- id `string` +- props [`StepfunctionstaskToSqsProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| existingQueueObj? | [`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html) | An optional, existing SQS queue to be used instead of the default queue. Providing both this and `queueProps` will cause an error. | +| queueProps? | [`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html) | Optional user-provided props to override the default props for the SQS queue. | +| deadLetterQueueProps? | [`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html) | Optional user-provided props to override the default props for the dead letter SQS queue. | +| deployDeadLetterQueue? | `boolean` | Whether to create a secondary queue to be used as a dead letter queue. Defaults to true. | +| maxReceiveCount? | `number` | The number of times a message can be unsuccessfully dequeued before being moved to the dead letter queue. Defaults to 15. | +| sqsSendMessageProps? | [`SqsSendMessageProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions-tasks.SqsSendMessageProps.html) | Optional user provided properties to create an SQS based stepfunctions task | + +## Pattern Properties + +| **Name** | **Type** | **Description** | +| :--------------- | :-------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| sqsQueue | [`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html) | Returns an instance of the SQS queue created by the pattern. | +| deadLetterQueue? | [`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html) | Returns an instance of the dead-letter SQS queue created by the pattern. | +| startState | [`State`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.State.html) | Returns the start state of the [`StateMachineFragment`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachineFragment.html) | +| endStates | [`States[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.State.html) | Returns an array of end states of the [`StateMachineFragment`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachineFragment.html) | + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon SQS Queue + +- Deploy SQS dead-letter queue for the source SQS Queue +- Enable server-side encryption for source SQS Queue using AWS Managed KMS Key +- Enforce encryption of data in transit + +### AWS StepFunctions Task + +- Configure limited privilege access IAM role for the StepFunctions Task function so that it can publish messages on the SQS queue +- Create a [`Fail`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.Fail.html) state it transition to if the task fails +- Add retry logic to the StepFunctions task, with a back-off-rate of 2, maximum number of attempts as 6 and interval duration of 3 seconds +- Create a message body for SQS queue with `input` starting at the root of the JSON using `$` and `taskToken` +- Define output path for the response from the stepfunctions task as `$` +- Define a `heartbeat` to be recieved by the stepfuntions for the [`State`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.State.html) to be considered 'active' atleast once in every 60 mins. +- Define a `timeout` of 720 mins (12 hours) after which the state will transition to the [`Fail`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.Fail.html) state in case a success or failure is not received + +## Architecture + +![Architecture Diagram](architecture.png) + +--- + +© Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/architecture.png b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b5dceee8cbf329ee7b7bf639142c24271f38ce7b GIT binary patch literal 87896 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
!Wc}1Gv2ilt!?>A*V|nKfT{Eg3>~1XVXO;ve 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 ui9p<V(wIvaB<>J#J0x>i~j(0+Y844 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts new file mode 100644 index 000000000..2aa296df6 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts @@ -0,0 +1,12 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json new file mode 100644 index 000000000..9c2710cbb --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json @@ -0,0 +1,100 @@ +{ + "name": "@aws-solutions-constructs/aws-stepfunctionstask-sqs", + "version": "0.0.0", + "description": "CDK constructs for defining a state machine fragement that an Amazon SQS queue and an AWS Lambda function.", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-assert": "cdk-integ-assert", + "integ-no-clean": "cdk-integ --no-clean", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.stepfunctionstasksqs", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "stepfunctionstasksqs" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.StepfunctionstaskSqs", + "packageId": "Amazon.Constructs.AWS.StepfunctionstaskSqs", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-stepfunctionstask-sqs", + "module": "aws_solutions_constructs.aws_stepfunctionstask_sqs" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lambda-event-sources": "0.0.0", + "@aws-cdk/aws-sqs": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-solutions-constructs/core": "0.0.0", + "constructs": "^3.2.0" + }, + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "@types/jest": "^26.0.22", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ], + "coverageReporters": [ + "text", + [ + "lcov", + { + "projectRoot": "../../../../" + } + ] + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lambda-event-sources": "0.0.0", + "@aws-cdk/aws-sqs": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-solutions-constructs/core": "0.0.0", + "constructs": "^3.2.0" + }, + "keywords": [ + "aws", + "cdk", + "awscdk", + "AWS Solutions Constructs", + "Amazon SQS", + "AWS Step Functions" + ] +} \ No newline at end of file From 5a7a2329723e6047bd9fa407b4617b001f46758c Mon Sep 17 00:00:00 2001 From: nihitkas Date: Tue, 27 Jul 2021 19:48:52 -0400 Subject: [PATCH 15/17] adding minimal deployable code to README --- .../aws-stepfunctionstask-sqs/README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md index ba80550bd..424dd4c4f 100644 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md @@ -25,12 +25,22 @@ | ![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript | `@aws-solutions-constructs/aws-stepfunctionstask-sqs` | | ![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java | `software.amazon.awsconstructs.services.stepfunctionstasksqs` | -This AWS Solutions Construct implements an Amazon SQS queue connected to an AWS Lambda function. +This AWS Solutions Construct implements an AWS Step Functions state machine task that is backed by an Amazon SQS queue. This queue can be integrated with any +compute option (not implemented within this construct); AWS Lambda, Amazon Fargate, Amazon EC2, Amazon EKS or Amazon ECS. -Here is a minimal deployable pattern definition in Typescipr: +Here is a minimal deployable pattern definition in Typesciprt: ```typescript -TBD; +import { StepfunctionstaskSqs } from "@aws-solutions-constructs/aws-stepfunctionstask-sqs"; +import * as sfn from "@aws-cdk/aws-stepfunctions"; + +const asyncTask = new StepfunctionstaskSqs(this, "SqsAsyncTask", {}); +asyncTask.next(new sfn.Succeed(this, "Success")); + +const workflowChain = sfn.Chain.start(asyncTask); +new StateMachine(this, "WorkflowEngine", { + definition: workflowChain, +}); ``` ## Initializer From 079f6f223b438912c3a362e03528d8572dcabaed Mon Sep 17 00:00:00 2001 From: nihitkas Date: Tue, 27 Jul 2021 19:55:18 -0400 Subject: [PATCH 16/17] adding minimal deployable code to README --- .../aws-stepfunctionstask-sqs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md index 424dd4c4f..b19620394 100644 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md @@ -26,7 +26,7 @@ | ![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java | `software.amazon.awsconstructs.services.stepfunctionstasksqs` | This AWS Solutions Construct implements an AWS Step Functions state machine task that is backed by an Amazon SQS queue. This queue can be integrated with any -compute option (not implemented within this construct); AWS Lambda, Amazon Fargate, Amazon EC2, Amazon EKS or Amazon ECS. +compute option (not implemented within this construct); AWS Lambda, Amazon Fargate, Amazon EC2, Amazon EKS or Amazon ECS. This construct builds the foundation pieces for the [`Service Integration - Callback with Task Token pattern`](https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html) defined for AWS Step Functions Here is a minimal deployable pattern definition in Typesciprt: From 3c0b58fc0dd12d8b031d14d776611b707c4128f5 Mon Sep 17 00:00:00 2001 From: nihitkas Date: Tue, 12 Oct 2021 12:52:12 -0400 Subject: [PATCH 17/17] fix for github issue #448 --- .../aws-kinesisstreams-gluejob/lib/index.ts | 7 +- .../aws-stepfunctionstask-sqs/.eslintignore | 4 - .../aws-stepfunctionstask-sqs/.gitignore | 15 --- .../aws-stepfunctionstask-sqs/.npmignore | 21 ---- .../aws-stepfunctionstask-sqs/README.md | 104 ------------------ .../architecture.png | Bin 87896 -> 0 bytes .../aws-stepfunctionstask-sqs/lib/index.ts | 12 -- .../aws-stepfunctionstask-sqs/package.json | 100 ----------------- .../core/lib/glue-job-helper.ts | 6 +- .../core/test/glue-job-helper.test.ts | 8 +- 10 files changed, 15 insertions(+), 262 deletions(-) delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/architecture.png delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts delete mode 100644 source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json diff --git a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts index 6b007f0db..502de438f 100644 --- a/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-gluejob/lib/index.ts @@ -119,6 +119,11 @@ export class KinesisstreamsToGluejob extends Construct { public readonly glueJobRole: IRole; public readonly database: glue.CfnDatabase; public readonly table: glue.CfnTable; + /** + * This property is only set if the Glue Job is created by the construct. If an exisiting Glue Job + * configuraton is supplied, the construct does not create an S3 bucket and hence the @outputBucket + * property is undefined + */ public readonly outputBucket?: [Bucket, (Bucket | undefined)?]; /** @@ -151,7 +156,7 @@ export class KinesisstreamsToGluejob extends Construct { }); } - [ this.glueJob, this.glueJobRole ] = defaults.buildGlueJob(this, { + [ this.glueJob, this.glueJobRole, this.outputBucket ] = defaults.buildGlueJob(this, { existingCfnJob: props.existingGlueJob, glueJobProps: props.glueJobProps, table: this.table!, diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore deleted file mode 100644 index 910cb0513..000000000 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -lib/*.js -test/*.js -*.d.ts -coverage \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore deleted file mode 100644 index 6773cabd2..000000000 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -lib/*.js -test/*.js -*.js.map -*.d.ts -node_modules -*.generated.ts -dist -.jsii - -.LAST_BUILD -.nyc_output -coverage -.nycrc -.LAST_PACKAGE -*.snk \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore deleted file mode 100644 index f66791629..000000000 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/.npmignore +++ /dev/null @@ -1,21 +0,0 @@ -# Exclude typescript source and config -*.ts -tsconfig.json -coverage -.nyc_output -*.tgz -*.snk -*.tsbuildinfo - -# Include javascript files and typescript declarations -!*.js -!*.d.ts - -# Exclude jsii outdir -dist - -# Include .jsii -!.jsii - -# Include .jsii -!.jsii \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md deleted file mode 100644 index b19620394..000000000 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# aws-stepfunctionstask-sqs module - - - ---- - -![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) - -> All classes are under active development and subject to non-backward compatible changes or removal in any -> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. -> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. - ---- - - - -| **Reference Documentation**: | https://docs.aws.amazon.com/solutions/latest/constructs/ | -| :--------------------------- | :------------------------------------------------------------------------------------------------ | - -
- -| **Language** | **Package** | -| :--------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -| ![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python | `aws_solutions_constructs.aws_stepfunctionstask_sqs` | -| ![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript | `@aws-solutions-constructs/aws-stepfunctionstask-sqs` | -| ![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java | `software.amazon.awsconstructs.services.stepfunctionstasksqs` | - -This AWS Solutions Construct implements an AWS Step Functions state machine task that is backed by an Amazon SQS queue. This queue can be integrated with any -compute option (not implemented within this construct); AWS Lambda, Amazon Fargate, Amazon EC2, Amazon EKS or Amazon ECS. This construct builds the foundation pieces for the [`Service Integration - Callback with Task Token pattern`](https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html) defined for AWS Step Functions - -Here is a minimal deployable pattern definition in Typesciprt: - -```typescript -import { StepfunctionstaskSqs } from "@aws-solutions-constructs/aws-stepfunctionstask-sqs"; -import * as sfn from "@aws-cdk/aws-stepfunctions"; - -const asyncTask = new StepfunctionstaskSqs(this, "SqsAsyncTask", {}); -asyncTask.next(new sfn.Succeed(this, "Success")); - -const workflowChain = sfn.Chain.start(asyncTask); -new StateMachine(this, "WorkflowEngine", { - definition: workflowChain, -}); -``` - -## Initializer - -```text -new StepfunctionstaskSqs(scope: Construct, id: string, props: StepfunctionstaskToSqsProps); -``` - -_Parameters_ - -- scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) -- id `string` -- props [`StepfunctionstaskToSqsProps`](#pattern-construct-props) - -## Pattern Construct Props - -| **Name** | **Type** | **Description** | -| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| existingQueueObj? | [`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html) | An optional, existing SQS queue to be used instead of the default queue. Providing both this and `queueProps` will cause an error. | -| queueProps? | [`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html) | Optional user-provided props to override the default props for the SQS queue. | -| deadLetterQueueProps? | [`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html) | Optional user-provided props to override the default props for the dead letter SQS queue. | -| deployDeadLetterQueue? | `boolean` | Whether to create a secondary queue to be used as a dead letter queue. Defaults to true. | -| maxReceiveCount? | `number` | The number of times a message can be unsuccessfully dequeued before being moved to the dead letter queue. Defaults to 15. | -| sqsSendMessageProps? | [`SqsSendMessageProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions-tasks.SqsSendMessageProps.html) | Optional user provided properties to create an SQS based stepfunctions task | - -## Pattern Properties - -| **Name** | **Type** | **Description** | -| :--------------- | :-------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| sqsQueue | [`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html) | Returns an instance of the SQS queue created by the pattern. | -| deadLetterQueue? | [`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html) | Returns an instance of the dead-letter SQS queue created by the pattern. | -| startState | [`State`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.State.html) | Returns the start state of the [`StateMachineFragment`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachineFragment.html) | -| endStates | [`States[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.State.html) | Returns an array of end states of the [`StateMachineFragment`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachineFragment.html) | - -## Default settings - -Out of the box implementation of the Construct without any override will set the following defaults: - -### Amazon SQS Queue - -- Deploy SQS dead-letter queue for the source SQS Queue -- Enable server-side encryption for source SQS Queue using AWS Managed KMS Key -- Enforce encryption of data in transit - -### AWS StepFunctions Task - -- Configure limited privilege access IAM role for the StepFunctions Task function so that it can publish messages on the SQS queue -- Create a [`Fail`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.Fail.html) state it transition to if the task fails -- Add retry logic to the StepFunctions task, with a back-off-rate of 2, maximum number of attempts as 6 and interval duration of 3 seconds -- Create a message body for SQS queue with `input` starting at the root of the JSON using `$` and `taskToken` -- Define output path for the response from the stepfunctions task as `$` -- Define a `heartbeat` to be recieved by the stepfuntions for the [`State`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.State.html) to be considered 'active' atleast once in every 60 mins. -- Define a `timeout` of 720 mins (12 hours) after which the state will transition to the [`Fail`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.Fail.html) state in case a success or failure is not received - -## Architecture - -![Architecture Diagram](architecture.png) - ---- - -© Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/architecture.png b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/architecture.png deleted file mode 100644 index b5dceee8cbf329ee7b7bf639142c24271f38ce7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87896 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
!Wc}1Gv2ilt!?>A*V|nKfT{Eg3>~1XVXO;ve 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 ui9p<V(wIvaB<>J#J0x>i~j(0+Y844 diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts deleted file mode 100644 index 2aa296df6..000000000 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/lib/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json b/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json deleted file mode 100644 index 9c2710cbb..000000000 --- a/source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs/package.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "name": "@aws-solutions-constructs/aws-stepfunctionstask-sqs", - "version": "0.0.0", - "description": "CDK constructs for defining a state machine fragement that an Amazon SQS queue and an AWS Lambda function.", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-solutions-constructs.git", - "directory": "source/patterns/@aws-solutions-constructs/aws-stepfunctionstask-sqs" - }, - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "scripts": { - "build": "tsc -b .", - "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", - "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", - "test": "jest --coverage", - "clean": "tsc -b --clean", - "watch": "tsc -b -w", - "integ": "cdk-integ", - "integ-assert": "cdk-integ-assert", - "integ-no-clean": "cdk-integ --no-clean", - "jsii": "jsii", - "jsii-pacmak": "jsii-pacmak", - "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", - "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" - }, - "jsii": { - "outdir": "dist", - "targets": { - "java": { - "package": "software.amazon.awsconstructs.services.stepfunctionstasksqs", - "maven": { - "groupId": "software.amazon.awsconstructs", - "artifactId": "stepfunctionstasksqs" - } - }, - "dotnet": { - "namespace": "Amazon.Constructs.AWS.StepfunctionstaskSqs", - "packageId": "Amazon.Constructs.AWS.StepfunctionstaskSqs", - "signAssembly": true, - "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" - }, - "python": { - "distName": "aws-solutions-constructs.aws-stepfunctionstask-sqs", - "module": "aws_solutions_constructs.aws_stepfunctionstask_sqs" - } - } - }, - "dependencies": { - "@aws-cdk/aws-lambda": "0.0.0", - "@aws-cdk/aws-lambda-event-sources": "0.0.0", - "@aws-cdk/aws-sqs": "0.0.0", - "@aws-cdk/aws-kms": "0.0.0", - "@aws-cdk/core": "0.0.0", - "@aws-solutions-constructs/core": "0.0.0", - "constructs": "^3.2.0" - }, - "devDependencies": { - "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.22", - "@types/node": "^10.3.0" - }, - "jest": { - "moduleFileExtensions": [ - "js" - ], - "coverageReporters": [ - "text", - [ - "lcov", - { - "projectRoot": "../../../../" - } - ] - ] - }, - "peerDependencies": { - "@aws-cdk/aws-lambda": "0.0.0", - "@aws-cdk/aws-lambda-event-sources": "0.0.0", - "@aws-cdk/aws-sqs": "0.0.0", - "@aws-cdk/aws-kms": "0.0.0", - "@aws-cdk/core": "0.0.0", - "@aws-solutions-constructs/core": "0.0.0", - "constructs": "^3.2.0" - }, - "keywords": [ - "aws", - "cdk", - "awscdk", - "AWS Solutions Constructs", - "Amazon SQS", - "AWS Step Functions" - ] -} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts index 51bc469ca..e76294544 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/glue-job-helper.ts @@ -78,7 +78,7 @@ export interface BuildGlueJobProps { readonly outputDataStore?: SinkDataStoreProps } -export function buildGlueJob(scope: Construct, props: BuildGlueJobProps): [glue.CfnJob, IRole] { +export function buildGlueJob(scope: Construct, props: BuildGlueJobProps): [glue.CfnJob, IRole, [Bucket, (Bucket | undefined)?]?] { if (!props.existingCfnJob) { if (props.glueJobProps) { if (props.glueJobProps.glueVersion === '2.0' && props.glueJobProps.maxCapacity) { @@ -101,7 +101,7 @@ export function buildGlueJob(scope: Construct, props: BuildGlueJobProps): [glue. } export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, database: glue.CfnDatabase, table: glue.CfnTable, - outputDataStore: SinkDataStoreProps): [glue.CfnJob, IRole] { + outputDataStore: SinkDataStoreProps): [glue.CfnJob, IRole, [Bucket, (Bucket | undefined)?]] { let _glueSecurityConfigName: string; @@ -183,7 +183,7 @@ export function deployGlueJob(scope: Construct, glueJobProps: glue.CfnJobProps, _scriptBucketLocation.grantRead(_jobRole); const _glueJob: glue.CfnJob = new glue.CfnJob(scope, 'KinesisETLJob', _newGlueJobProps); - return [_glueJob, _jobRole]; + return [_glueJob, _jobRole, _outputLocation]; } /** diff --git a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts index 732f893ff..5b0629469 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/glue-job-helper.test.ts @@ -42,7 +42,7 @@ test('Test deployment with role creation', () => { const _database = defaults.createGlueDatabase(stack, defaults.DefaultGlueDatabaseProps()); - defaults.buildGlueJob(stack, { + const _glueJob = defaults.buildGlueJob(stack, { glueJobProps: cfnJobProps, database: _database, table: defaults.createGlueTable(stack, _database, undefined, [{ @@ -52,6 +52,8 @@ test('Test deployment with role creation', () => { }], 'kinesis', {STREAM_NAME: 'testStream'}) }); + expect(_glueJob[2]?.[0]).toBeDefined(); + expect(_glueJob[2]?.[0]).toBeInstanceOf(Bucket); expect(stack).toHaveResourceLike('AWS::Glue::Job', { Type: "AWS::Glue::Job", Properties: { @@ -99,7 +101,7 @@ test('Create a Glue Job outside the construct', () => { const _database = defaults.createGlueDatabase(stack, defaults.DefaultGlueDatabaseProps()); - defaults.buildGlueJob(stack, { + const _glueJob = defaults.buildGlueJob(stack, { existingCfnJob: _existingCfnJob, outputDataStore: { datastoreType: defaults.SinkStoreType.S3 @@ -111,6 +113,8 @@ test('Create a Glue Job outside the construct', () => { comment: "" }], 'kinesis', {STREAM_NAME: 'testStream'}) }); + + expect(_glueJob[2]).not.toBeDefined(); expect(stack).toHaveResourceLike('AWS::Glue::Job', { Type: "AWS::Glue::Job", Properties: {