diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/.eslintrc.js b/packages/@aws-cdk/aws-kinesisanalytics-flink/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/.gitignore b/packages/@aws-cdk/aws-kinesisanalytics-flink/.gitignore new file mode 100644 index 0000000000000..d8a8561d50885 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk +!.eslintrc.js +!jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/.npmignore b/packages/@aws-cdk/aws-kinesisanalytics-flink/.npmignore new file mode 100644 index 0000000000000..63ab95621c764 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/.npmignore @@ -0,0 +1,27 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/LICENSE b/packages/@aws-cdk/aws-kinesisanalytics-flink/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2021 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. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/NOTICE b/packages/@aws-cdk/aws-kinesisanalytics-flink/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md b/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md new file mode 100644 index 0000000000000..7dc9b1a088b16 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md @@ -0,0 +1,74 @@ +# Kinesis Analytics Flink + + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. 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. + +--- + + + +This package provides constructs for creating Kinesis Analytics Flink +applications. To learn more about using using managed Flink applications, see +the [AWS developer +guide](https://docs.aws.amazon.com/kinesisanalytics/latest/java/what-is.html). + +## Creating Flink Applications + +To create a new Flink application, use the `Application` construct: + +[simple flink application](test/integ.application.lit.ts) + +The `code` property can use `fromAsset` as shown above to reference a local jar +file in s3 or `fromBucket` to reference a file in s3. + +[flink application using code from bucket](test/integ.application-code-from-bucket.lit.ts) + +The `propertyGroups` property provides a way of passing arbitrary runtime +properties to your Flink application. You can use the +aws-kinesisanalytics-runtime library to [retrieve these +properties](https://docs.aws.amazon.com/kinesisanalytics/latest/java/how-properties.html#how-properties-access). + +```ts +import * as flink from '@aws-cdk/aws-kinesisanalytics-flink'; + +const flinkApp = new flink.Application(this, 'Application', { + // ... + propertyGroups: { + FlinkApplicationProperties: { + inputStreamName: 'my-input-kinesis-stream', + outputStreamName: 'my-output-kinesis-stream', + }, + }, +}); +``` + +Flink applications also have specific configuration for passing parameters +when the Flink job starts. These include parameters for checkpointing, +snapshotting, monitoring, and parallelism. + +```ts +import * as logs from '@aws-cdk/aws-logs'; + +const flinkApp = new flink.Application(this, 'Application', { + code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), + runtime: file.Runtime.FLINK_1_11, + checkpointingEnabled: true, // default is true + checkpointInterval: cdk.Duration.seconds(30), // default is 1 minute + minPausesBetweenCheckpoints: cdk.Duration.seconds(10), // default is 5 seconds + logLevel: flink.LogLevel.ERROR, // default is INFO + metricsLevel: flink.MetricsLevel.PARALLELISM, // default is APPLICATION + autoScalingEnabled: false, // default is true + parallelism: 32, // default is 1 + parallelismPerKpu: 2, // default is 1 + snapshotsEnabled: false, // default is true + logGroup: new logs.LogGroup(this, 'LogGroup'), // by default, a new LogGroup will be created +}); +``` diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/jest.config.js b/packages/@aws-cdk/aws-kinesisanalytics-flink/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application-code.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application-code.ts new file mode 100644 index 0000000000000..c7d7b49180086 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application-code.ts @@ -0,0 +1,139 @@ +import * as ka from '@aws-cdk/aws-kinesisanalytics'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import { Construct } from '@aws-cdk/core'; + +/** + * The return type of {@link ApplicationCode.bind}. This represents + * CloudFormation configuration and an s3 bucket holding the Flink application + * JAR file. + */ +export interface ApplicationCodeConfig { + /** + * Low-level Cloudformation ApplicationConfigurationProperty + */ + readonly applicationCodeConfigurationProperty: ka.CfnApplicationV2.ApplicationConfigurationProperty; + + /** + * S3 Bucket that stores the Flink application code + */ + readonly bucket: s3.IBucket; +} + +/** + * Code configuration providing the location to a Flink application JAR file. + */ +export abstract class ApplicationCode { + /** + * Reference code from an S3 bucket. + * + * @param bucket - an s3 bucket + * @param fileKey - a key pointing to a Flink JAR file + * @param objectVersion - an optional version string for the provided fileKey + */ + public static fromBucket(bucket: s3.IBucket, fileKey: string, objectVersion?: string): ApplicationCode { + return new BucketApplicationCode({ + bucket, + fileKey, + objectVersion, + }); + } + + /** + * Reference code from a local directory containing a Flink JAR file. + * + * @param path - a local directory path + * @parm options - standard s3 AssetOptions + */ + public static fromAsset(path: string, options?: s3_assets.AssetOptions): ApplicationCode { + return new AssetApplicationCode(path, options); + } + + /** + * A method to lazily bind asset resources to the parent FlinkApplication. + */ + public abstract bind(scope: Construct): ApplicationCodeConfig; +} + +interface BucketApplicationCodeProps { + readonly bucket: s3.IBucket; + readonly fileKey: string; + readonly objectVersion?: string; +} + +class BucketApplicationCode extends ApplicationCode { + public readonly bucket?: s3.IBucket; + public readonly fileKey: string; + public readonly objectVersion?: string; + + constructor(props: BucketApplicationCodeProps) { + super(); + this.bucket = props.bucket; + this.fileKey = props.fileKey; + this.objectVersion = props.objectVersion; + } + + public bind(_scope: Construct): ApplicationCodeConfig { + return { + applicationCodeConfigurationProperty: { + applicationCodeConfiguration: { + codeContent: { + s3ContentLocation: { + bucketArn: this.bucket!.bucketArn, + fileKey: this.fileKey, + objectVersion: this.objectVersion, + }, + }, + codeContentType: 'ZIPFILE', + }, + }, + bucket: this.bucket!, + }; + } +} + +class AssetApplicationCode extends ApplicationCode { + private readonly path: string; + private readonly options?: s3_assets.AssetOptions; + private _asset?: s3_assets.Asset; + + constructor(path: string, options?: s3_assets.AssetOptions) { + super(); + this.path = path; + this.options = options; + } + + public bind(scope: Construct): ApplicationCodeConfig { + this._asset = new s3_assets.Asset(scope, 'Code', { + path: this.path, + ...this.options, + }); + + if (!this._asset.isZipArchive) { + throw new Error(`Asset must be a .zip file or a directory (${this.path})`); + } + + return { + applicationCodeConfigurationProperty: { + applicationCodeConfiguration: { + codeContent: { + s3ContentLocation: { + bucketArn: this._asset.bucket.bucketArn, + fileKey: this._asset.s3ObjectKey, + }, + }, + codeContentType: 'ZIPFILE', + }, + }, + bucket: this._asset.bucket, + }; + } + + get asset(): s3_assets.Asset | undefined { + return this._asset; + } + + get bucket(): s3.IBucket | undefined { + return this._asset?.bucket; + } +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts new file mode 100644 index 0000000000000..1bcfa0fc4cbbf --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts @@ -0,0 +1,341 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as ka from '@aws-cdk/aws-kinesisanalytics'; +import * as logs from '@aws-cdk/aws-logs'; +import * as core from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { ApplicationCode } from './application-code'; +import { environmentProperties } from './private/environment-properties'; +import { flinkApplicationConfiguration } from './private/flink-application-configuration'; +import { validateFlinkApplicationProps as validateApplicationProps } from './private/validation'; +import { LogLevel, MetricsLevel, PropertyGroups, Runtime } from './types'; + +/** + * An interface expressing the public properties on both an imported and + * CDK-created Flink application. + */ +export interface IApplication extends core.IResource, iam.IGrantable { + /** + * The application ARN. + * + * @attribute + */ + readonly applicationArn: string; + + /** + * The name of the Flink application. + * + * @attribute + */ + readonly applicationName: string; + + /** + * The application IAM role. + */ + readonly role?: iam.IRole; + + /** + * Convenience method for adding a policy statement to the application role. + */ + addToRolePolicy(policyStatement: iam.PolicyStatement): boolean; +} + +/** + * Implements the functionality shared between CDK created and imported + * IApplications. + */ +abstract class ApplicationBase extends core.Resource implements IApplication { + public abstract readonly applicationArn: string; + public abstract readonly applicationName: string; + public abstract readonly role?: iam.IRole; + + // Implement iam.IGrantable interface + public abstract readonly grantPrincipal: iam.IPrincipal; + + /** Implement the convenience {@link IApplication.addToPrincipalPolicy} method. */ + public addToRolePolicy(policyStatement: iam.PolicyStatement): boolean { + if (this.role) { + this.role.addToPrincipalPolicy(policyStatement); + return true; + } + + return false; + } +} + +/** + * Props for creating an Application construct. + */ +export interface ApplicationProps { + /** + * A name for your Application that is unique to an AWS account. + * + * @default - CloudFormation-generated name + */ + readonly applicationName?: string; + + /** + * The Flink version to use for this application. + */ + readonly runtime: Runtime; + + /** + * The Flink code asset to run. + */ + readonly code: ApplicationCode; + + /** + * Whether checkpointing is enabled while your application runs. + * + * @default true + */ + readonly checkpointingEnabled?: boolean; + + /** + * The interval between checkpoints. + * + * @default 1 minute + */ + readonly checkpointInterval?: core.Duration; + + /** + * The minimum amount of time in to wait after a checkpoint finishes to start + * a new checkpoint. + * + * @default 5 seconds + */ + readonly minPauseBetweenCheckpoints?: core.Duration; + + /** + * The level of log verbosity from the Flink application. + * + * @default FlinkLogLevel.INFO + */ + readonly logLevel?: LogLevel; + + /** + * Describes the granularity of the CloudWatch metrics for an application. + * Use caution with Parallelism level metrics. Parallelism granularity logs + * metrics for each parallel thread and can quickly become expensive when + * parallelism is high (e.g. > 64). + * + * @default MetricsLevel.APPLICATION + */ + readonly metricsLevel?: MetricsLevel; + + /** + * Whether the Kinesis Data Analytics service can increase the parallelism of + * the application in response to resource usage. + * + * @default true + */ + readonly autoScalingEnabled?: boolean; + + /** + * The initial parallelism for the application. Kinesis Data Analytics can + * stop the app, increase the parallelism, and start the app again if + * autoScalingEnabled is true (the default value). + * + * @default 1 + */ + readonly parallelism?: number; + + /** + * The Flink parallelism allowed per Kinesis Processing Unit (KPU). + * + * @default 1 + */ + readonly parallelismPerKpu?: number + + /** + * Determines if Flink snapshots are enabled. + * + * @default true + */ + readonly snapshotsEnabled?: boolean; + + /** + * Configuration PropertyGroups. You can use these property groups to pass + * arbitrary runtime configuration values to your Flink app. + * + * @default No property group configuration provided to the Flink app + */ + readonly propertyGroups?: PropertyGroups; + + /** + * A role to use to grant permissions to your application. Prefer omitting + * this property and using the default role. + * + * @default - a new Role will be created + */ + readonly role?: iam.IRole; + + /** + * Provide a RemovalPolicy to override the default. + * + * @default RemovalPolicy.DESTROY + */ + readonly removalPolicy?: core.RemovalPolicy; + + /** + * The log group to send log entries to. + * + * @default CDK's default LogGroup + */ + readonly logGroup?: logs.ILogGroup; +} + +/** + * An imported Flink application. + */ +class Import extends ApplicationBase { + public readonly grantPrincipal: iam.IPrincipal; + public readonly role?: iam.IRole; + public readonly applicationName: string; + public readonly applicationArn: string; + + constructor(scope: Construct, id: string, attrs: { applicationArn: string, applicationName: string }) { + super(scope, id); + + // Imported applications have no associated role or grantPrincipal + this.grantPrincipal = new iam.UnknownPrincipal({ resource: this }); + this.role = undefined; + + this.applicationArn = attrs.applicationArn; + this.applicationName = attrs.applicationName; + } +} + +/** + * The L2 construct for Flink Kinesis Data Applications. + * + * @resource AWS::KinesisAnalyticsV2::Application + * + * @experimental + */ +export class Application extends ApplicationBase { + /** + * Import an existing Flink application defined outside of CDK code by + * applicationName. + */ + public static fromApplicationName(scope: Construct, id: string, applicationName: string): IApplication { + const applicationArn = core.Stack.of(scope).formatArn(applicationArnComponents(applicationName)); + + return new Import(scope, id, { applicationArn, applicationName }); + } + + /** + * Import an existing application defined outside of CDK code by + * applicationArn. + */ + public static fromApplicationArn(scope: Construct, id: string, applicationArn: string): IApplication { + const applicationName = core.Stack.of(scope).parseArn(applicationArn).resourceName; + if (!applicationName) { + throw new Error(`applicationArn for fromApplicationArn (${applicationArn}) must include resource name`); + } + + return new Import(scope, id, { applicationArn, applicationName }); + } + + public readonly applicationArn: string; + public readonly applicationName: string; + + // Role must be optional for JSII compatibility + public readonly role?: iam.IRole; + + public readonly grantPrincipal: iam.IPrincipal; + + constructor(scope: Construct, id: string, props: ApplicationProps) { + super(scope, id, { physicalName: props.applicationName }); + validateApplicationProps(props); + + this.role = props.role ?? new iam.Role(this, 'Role', { + assumedBy: new iam.ServicePrincipal('kinesisanalytics.amazonaws.com'), + }); + this.grantPrincipal = this.role; + + // Permit metric publishing to CloudWatch + this.role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['cloudwatch:PutMetricData'], + resources: ['*'], + })); + + const code = props.code.bind(this); + code.bucket.grantRead(this); + + const resource = new ka.CfnApplicationV2(this, 'Resource', { + runtimeEnvironment: props.runtime.value, + serviceExecutionRole: this.role.roleArn, + applicationConfiguration: { + ...code.applicationCodeConfigurationProperty, + environmentProperties: environmentProperties(props.propertyGroups), + flinkApplicationConfiguration: flinkApplicationConfiguration({ + checkpointingEnabled: props.checkpointingEnabled, + checkpointInterval: props.checkpointInterval, + minPauseBetweenCheckpoints: props.minPauseBetweenCheckpoints, + logLevel: props.logLevel, + metricsLevel: props.metricsLevel, + autoScalingEnabled: props.autoScalingEnabled, + parallelism: props.parallelism, + parallelismPerKpu: props.parallelismPerKpu, + }), + applicationSnapshotConfiguration: { + snapshotsEnabled: props.snapshotsEnabled ?? true, + }, + }, + }); + resource.node.addDependency(this.role); + + const logGroup = props.logGroup ?? new logs.LogGroup(this, 'LogGroup'); + const logStream = new logs.LogStream(this, 'LogStream', { logGroup }); + + /* Permit logging */ + + this.role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['logs:DescribeLogGroups'], + resources: [ + core.Stack.of(this).formatArn({ + service: 'logs', + resource: 'log-group', + sep: ':', + resourceName: '*', + }), + ], + })); + + this.role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['logs:DescribeLogStreams'], + resources: [logGroup.logGroupArn], + })); + + const logStreamArn = `arn:${core.Aws.PARTITION}:logs:${core.Aws.REGION}:${core.Aws.ACCOUNT_ID}:log-group:${logGroup.logGroupName}:log-stream:${logStream.logStreamName}`; + this.role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['logs:PutLogEvents'], + resources: [logStreamArn], + })); + + new ka.CfnApplicationCloudWatchLoggingOptionV2(this, 'LoggingOption', { + applicationName: resource.ref, + cloudWatchLoggingOption: { + logStreamArn, + }, + }); + + this.applicationName = this.getResourceNameAttribute(resource.ref); + this.applicationArn = this.getResourceArnAttribute( + core.Stack.of(this).formatArn(applicationArnComponents(resource.ref)), + applicationArnComponents(this.physicalName), + ); + + resource.applyRemovalPolicy(props.removalPolicy, { + default: core.RemovalPolicy.DESTROY, + }); + } +} + +function applicationArnComponents(resourceName: string): core.ArnComponents { + return { + service: 'kinesisanalytics', + resource: 'application', + resourceName, + }; +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/index.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/index.ts new file mode 100644 index 0000000000000..6e2bea43a0ebf --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/index.ts @@ -0,0 +1,4 @@ +export * from './application'; +export * from './application-code'; +export * from './types'; + diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/environment-properties.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/environment-properties.ts new file mode 100644 index 0000000000000..d13a0e870e23e --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/environment-properties.ts @@ -0,0 +1,16 @@ +import * as ka from '@aws-cdk/aws-kinesisanalytics'; +import { PropertyGroups } from '../types'; + +export function environmentProperties(propertyGroups?: PropertyGroups): ka.CfnApplicationV2.EnvironmentPropertiesProperty | undefined { + const entries = Object.entries(propertyGroups ?? {}); + if (entries.length === 0) { + return; + } + + return { + propertyGroups: entries.map(([id, map]) => ({ + propertyGroupId: id, + propertyMap: map, + })), + }; +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/flink-application-configuration.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/flink-application-configuration.ts new file mode 100644 index 0000000000000..c0a93edac20d6 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/flink-application-configuration.ts @@ -0,0 +1,78 @@ +import * as core from '@aws-cdk/core'; +import { LogLevel, MetricsLevel } from '../types'; + +interface FlinkApplicationConfiguration extends + CheckpointConfiguration, + MonitoringConfiguration, + ParallelismConfiguration {} + +interface CheckpointConfiguration { + checkpointingEnabled?: boolean; + checkpointInterval?: core.Duration; + minPauseBetweenCheckpoints?: core.Duration; +} + +interface MonitoringConfiguration { + logLevel?: LogLevel; + metricsLevel?: MetricsLevel; +} + +interface ParallelismConfiguration { + autoScalingEnabled?: boolean; + parallelism?: number; + parallelismPerKpu?: number; +} + +/** + * Build the nested Cfn FlinkApplicationConfiguration object. This function + * doesn't return empty config objects, returning the minimal config needed to + * express the supplied properties. + * + * This function also handles the quirky configType: 'CUSTOM' setting required + * whenever config in one of the nested groupings. + */ +export function flinkApplicationConfiguration(config: FlinkApplicationConfiguration) { + const checkpointConfiguration = configFor({ + checkpointingEnabled: config.checkpointingEnabled, + checkpointInterval: config.checkpointInterval?.toMilliseconds(), + minPauseBetweenCheckpoints: config.minPauseBetweenCheckpoints?.toMilliseconds(), + }); + + const monitoringConfiguration = configFor({ + logLevel: config.logLevel, + metricsLevel: config.metricsLevel, + }); + + const parallelismConfiguration = configFor({ + autoScalingEnabled: config.autoScalingEnabled, + parallelism: config.parallelism, + parallelismPerKpu: config.parallelismPerKpu, + }); + + const applicationConfiguration = { + checkpointConfiguration, + monitoringConfiguration, + parallelismConfiguration, + }; + + if (isEmptyObj(applicationConfiguration)) { + return; + } + + return applicationConfiguration; +} + +function configFor(config: {[key: string]: unknown}) { + if (isEmptyObj(config)) { + return; + } + + return { + ...config, + configurationType: 'CUSTOM', + }; +} + +function isEmptyObj(obj: {[key: string]: unknown}) { + return Object.values(obj).every(v => v === undefined); +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/validation.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/validation.ts new file mode 100644 index 0000000000000..b0f94f56daf77 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/private/validation.ts @@ -0,0 +1,54 @@ +import * as core from '@aws-cdk/core'; + +interface ValidatedProps { + applicationName?: string; + parallelism?: number; + parallelismPerKpu?: number; +} + +/** + * Early validation for the props used to create FlinkApplications. + */ +export function validateFlinkApplicationProps(props: ValidatedProps) { + validateApplicationName(props.applicationName); + validateParallelism(props.parallelism); + validateParallelismPerKpu(props.parallelismPerKpu); +} + +function validateApplicationName(applicationName?: string) { + if (applicationName === undefined || core.Token.isUnresolved(applicationName)) { + return; + } + + if (applicationName.length === 0) { + throw new Error('applicationName cannot be empty. It must contain at least 1 character.'); + } + + if (!/^[a-zA-Z0-9_.-]+$/.test(applicationName)) { + throw new Error(`applicationName may only contain letters, numbers, underscores, hyphens, and periods. Name: ${applicationName}`); + } + + if (applicationName.length > 128) { + throw new Error(`applicationName max length is 128. Name: ${applicationName} is ${applicationName.length} characters.`); + } +} + +function validateParallelism(parallelism?: number) { + if (parallelism === undefined || core.Token.isUnresolved(parallelism)) { + return; + } + + if (parallelism < 1) { + throw new Error('parallelism must be at least 1'); + } +} + +function validateParallelismPerKpu(parallelismPerKpu?: number) { + if (parallelismPerKpu === undefined || core.Token.isUnresolved(parallelismPerKpu)) { + return; + } + + if (parallelismPerKpu < 1) { + throw new Error('parallelismPerKpu must be at least 1'); + } +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts new file mode 100644 index 0000000000000..b92d55cb48518 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts @@ -0,0 +1,67 @@ +/** + * Available log levels for Flink applications. + */ +export enum LogLevel { + /** Debug level logging */ + DEBUG = 'DEBUG', + + /** Info level logging */ + INFO = 'INFO', + + /** Warn level logging */ + WARN = 'WARN', + + /** Error level logging */ + ERROR = 'ERROR', +} + +/** + * Granularity of metrics sent to CloudWatch. + */ +export enum MetricsLevel { + /** Application sends the least metrics to CloudWatch */ + APPLICATION = 'APPLICATION', + + /** Task includes task-level metrics sent to CloudWatch */ + TASK = 'TASK', + + /** Operator includes task-level and operator-level metrics sent to CloudWatch */ + OPERATOR = 'OPERATOR', + + /** Send all metrics including metrics per task thread */ + PARALLELISM = 'PARALLELISM', +} + +/** + * Interface for building AWS::KinesisAnalyticsV2::Application PropertyGroup + * configuration. + */ +export interface PropertyGroups { + readonly [propertyId: string]: {[mapKey: string]: string}; +} + +/** + * Available Flink runtimes for Kinesis Analytics. + */ +export class Runtime { + /** Flink Version 1.6 */ + public static readonly FLINK_1_6 = Runtime.of('FLINK-1_6'); + + /** Flink Version 1.8 */ + public static readonly FLINK_1_8 = Runtime.of('FLINK-1_8'); + + /** Flink Version 1.11 */ + public static readonly FLINK_1_11 = Runtime.of('FLINK-1_11'); + + /** Create a new Runtime with with an arbitrary Flink version string */ + public static of(value: string) { + return new Runtime(value); + } + + /** The Cfn string that represents a version of Flink */ + public readonly value: string; + + private constructor(value: string) { + this.value = value; + } +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json b/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json new file mode 100644 index 0000000000000..80dbdfd4aeb76 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json @@ -0,0 +1,115 @@ +{ + "name": "@aws-cdk/aws-kinesisanalytics-flink", + "version": "0.0.0", + "description": "A CDK Construct Library for Kinesis Analytics Flink applications", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.kinesis.analytics.flink", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "kinesisanalytics-flink" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.KinesisAnalyticsFlink", + "packageId": "Amazon.CDK.AWS.KinesisAnalyticsFlink", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.aws-kinesisanalytics-flink", + "module": "aws_cdk.aws_kinesisanalytics_flink", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] + } + }, + "projectReferences": true + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-kinesisanalytics-flink" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "rosetta:extract": "yarn --silent jsii-rosetta extract" + }, + "keywords": [ + "aws", + "cdk", + "kinesis", + "analytics", + "kinesisanalytcs", + "flink" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", + "jest": "^26.6.3", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/assets": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kinesisanalytics": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "constructs": "^3.2.0" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "@aws-cdk/assets": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kinesisanalytics": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "constructs": "^3.2.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-kinesisanalytics-flink.ApplicationProps" + ] + }, + "stability": "experimental", + "maturity": "experimental", + "awscdkio": { + "announce": false + }, + "cdk-build": { + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + } +} diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/test/application.test.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/application.test.ts new file mode 100644 index 0000000000000..6dc35fadd44b6 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/application.test.ts @@ -0,0 +1,604 @@ +import { arrayWith, objectLike, ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as iam from '@aws-cdk/aws-iam'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as core from '@aws-cdk/core'; +import * as path from 'path'; +import * as flink from '../lib'; + +describe('Application', () => { + let stack: core.Stack; + let bucket: s3.Bucket; + let requiredProps: { + runtime: flink.Runtime; + code: flink.ApplicationCode; + }; + + beforeEach(() => { + stack = new core.Stack(); + bucket = new s3.Bucket(stack, 'CodeBucket'); + requiredProps = { + runtime: flink.Runtime.FLINK_1_11, + code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), + }; + }); + + test('default Flink Application', () => { + new flink.Application(stack, 'FlinkApplication', { + runtime: flink.Runtime.FLINK_1_11, + code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), + }); + + expect(stack).toHaveResource('AWS::KinesisAnalyticsV2::Application', { + RuntimeEnvironment: 'FLINK-1_11', + ServiceExecutionRole: { + 'Fn::GetAtt': [ + 'FlinkApplicationRole2F7BCBF6', + 'Arn', + ], + }, + ApplicationConfiguration: { + ApplicationCodeConfiguration: { + CodeContent: { + S3ContentLocation: { + BucketARN: stack.resolve(bucket.bucketArn), + FileKey: 'my-app.jar', + }, + }, + CodeContentType: 'ZIPFILE', + }, + ApplicationSnapshotConfiguration: { + SnapshotsEnabled: true, + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + DeletionPolicy: 'Delete', + }, ResourcePart.CompleteDefinition); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'kinesisanalytics.amazonaws.com', + }, + }], + Version: '2012-10-17', + }, + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith( + { Action: 'cloudwatch:PutMetricData', Effect: 'Allow', Resource: '*' }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: { + // looks like arn:aws:logs:us-east-1:123456789012:log-group:*, + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':logs:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':log-group:*', + ]], + }, + }, + { + Action: 'logs:DescribeLogStreams', + Effect: 'Allow', + Resource: { + // looks like: arn:aws:logs:us-east-1:123456789012:log-group:my-log-group:*, + 'Fn::GetAtt': ['FlinkApplicationLogGroup7739479C', 'Arn'], + }, + }, + { + Action: 'logs:PutLogEvents', + Effect: 'Allow', + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':logs:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':log-group:', + { Ref: 'FlinkApplicationLogGroup7739479C' }, + ':log-stream:', + { Ref: 'FlinkApplicationLogStreamB633AF32' }, + ]], + }, + }, + ), + }, + }); + }); + + test('providing a custom role', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + role: new iam.Role(stack, 'CustomRole', { + assumedBy: new iam.ServicePrincipal('custom-principal'), + }), + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'custom-principal.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('addToPrincipalPolicy', () => { + const app = new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + }); + + app.addToRolePolicy(new iam.PolicyStatement({ + actions: ['custom:action'], + resources: ['*'], + })); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith( + objectLike({ Action: 'custom:action', Effect: 'Allow', Resource: '*' }), + ), + }, + }); + }); + + test('providing a custom runtime', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + runtime: flink.Runtime.of('custom'), + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + RuntimeEnvironment: 'custom', + }); + }); + + test('providing a custom removal policy', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + removalPolicy: core.RemovalPolicy.RETAIN, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + DeletionPolicy: 'Retain', + }, ResourcePart.CompleteDefinition); + }); + + test('granting permissions to resources', () => { + const app = new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + }); + + const dataBucket = new s3.Bucket(stack, 'DataBucket'); + dataBucket.grantRead(app); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: arrayWith( + objectLike({ Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'] }), + ), + }, + }); + }); + + test('using an asset for code', () => { + const code = flink.ApplicationCode.fromAsset(path.join(__dirname, 'code-asset')); + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + code, + }); + const assetRef = 'AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67'; + const versionKeyRef = 'AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E'; + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + ApplicationCodeConfiguration: { + CodeContent: { + S3ContentLocation: { + BucketARN: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':s3:::', + { Ref: assetRef }, + ]], + }, + FileKey: { + 'Fn::Join': ['', [ + { 'Fn::Select': [0, { 'Fn::Split': ['||', { Ref: versionKeyRef }] }] }, + { 'Fn::Select': [1, { 'Fn::Split': ['||', { Ref: versionKeyRef }] }] }, + ]], + }, + }, + }, + CodeContentType: 'ZIPFILE', + }, + }, + }); + }); + + test('adding property groups', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + propertyGroups: { + FlinkApplicationProperties: { + SomeProperty: 'SomeValue', + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + EnvironmentProperties: { + PropertyGroups: [ + { + PropertyGroupId: 'FlinkApplicationProperties', + PropertyMap: { + SomeProperty: 'SomeValue', + }, + }, + ], + }, + }, + }); + }); + + test('checkpointEnabled setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + checkpointingEnabled: false, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + CheckpointConfiguration: { + ConfigurationType: 'CUSTOM', + CheckpointingEnabled: false, + }, + }, + }, + }); + }); + + test('checkpointInterval setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + checkpointInterval: core.Duration.minutes(5), + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + CheckpointConfiguration: { + ConfigurationType: 'CUSTOM', + CheckpointInterval: 300_000, + }, + }, + }, + }); + }); + + test('minPauseBetweenCheckpoints setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + minPauseBetweenCheckpoints: core.Duration.seconds(10), + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + CheckpointConfiguration: { + ConfigurationType: 'CUSTOM', + MinPauseBetweenCheckpoints: 10_000, + }, + }, + }, + }); + }); + + test('logLevel setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + logLevel: flink.LogLevel.DEBUG, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + MonitoringConfiguration: { + ConfigurationType: 'CUSTOM', + LogLevel: 'DEBUG', + }, + }, + }, + }); + }); + + test('metricsLevel setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + metricsLevel: flink.MetricsLevel.PARALLELISM, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + MonitoringConfiguration: { + ConfigurationType: 'CUSTOM', + MetricsLevel: 'PARALLELISM', + }, + }, + }, + }); + }); + + test('autoscalingEnabled setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + autoScalingEnabled: false, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + ParallelismConfiguration: { + ConfigurationType: 'CUSTOM', + AutoScalingEnabled: false, + }, + }, + }, + }); + }); + + test('parallelism setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + parallelism: 2, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + ParallelismConfiguration: { + ConfigurationType: 'CUSTOM', + Parallelism: 2, + }, + }, + }, + }); + }); + + test('parallelismPerKpu setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + parallelismPerKpu: 2, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + FlinkApplicationConfiguration: { + ParallelismConfiguration: { + ConfigurationType: 'CUSTOM', + ParallelismPerKPU: 2, + }, + }, + }, + }); + }); + + test('snapshotsEnabled setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + snapshotsEnabled: false, + }); + + expect(stack).toHaveResourceLike('AWS::KinesisAnalyticsV2::Application', { + ApplicationConfiguration: { + ApplicationSnapshotConfiguration: { + SnapshotsEnabled: false, + }, + }, + }); + }); + + test('default logging option', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + snapshotsEnabled: false, + }); + + expect(stack).toHaveResource('AWS::KinesisAnalyticsV2::ApplicationCloudWatchLoggingOption', { + ApplicationName: { + Ref: 'FlinkApplicationC5836815', + }, + CloudWatchLoggingOption: { + LogStreamARN: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'FlinkApplicationLogGroup7739479C', + }, + ':log-stream:', + { + Ref: 'FlinkApplicationLogStreamB633AF32', + }, + ], + ], + }, + }, + }); + + expect(stack).toHaveResource('AWS::Logs::LogGroup', { + Properties: { + RetentionInDays: 731, + }, + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', + }, ResourcePart.CompleteDefinition); + + expect(stack).toHaveResource('AWS::Logs::LogStream', { + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', + }, ResourcePart.CompleteDefinition); + }); + + test('logGroup setting', () => { + new flink.Application(stack, 'FlinkApplication', { + ...requiredProps, + logGroup: new logs.LogGroup(stack, 'LogGroup', { + logGroupName: 'custom', + }), + }); + + expect(stack).toHaveResource('AWS::Logs::LogGroup', { + LogGroupName: 'custom', + }); + }); + + test('validating applicationName', () => { + // Expect no error with valid name + new flink.Application(stack, 'ValidString', { + ...requiredProps, + applicationName: 'my-VALID.app_name', + }); + + // Expect no error with ref + new flink.Application(stack, 'ValidRef', { + ...requiredProps, + applicationName: new core.CfnParameter(stack, 'Parameter').valueAsString, + }); + + expect(() => { + new flink.Application(stack, 'Empty', { + ...requiredProps, + applicationName: '', + }); + }).toThrow(/cannot be empty/); + + expect(() => { + new flink.Application(stack, 'InvalidCharacters', { + ...requiredProps, + applicationName: '!!!', + }); + }).toThrow(/may only contain letters, numbers, underscores, hyphens, and periods/); + + expect(() => { + new flink.Application(stack, 'TooLong', { + ...requiredProps, + applicationName: 'a'.repeat(129), + }); + }).toThrow(/max length is 128/); + }); + + test('validating parallelism', () => { + // Expect no error with valid value + new flink.Application(stack, 'ValidNumber', { + ...requiredProps, + parallelism: 32, + }); + + // Expect no error with ref + new flink.Application(stack, 'ValidRef', { + ...requiredProps, + parallelism: new core.CfnParameter(stack, 'Parameter', { + type: 'Number', + }).valueAsNumber, + }); + + expect(() => { + new flink.Application(stack, 'TooSmall', { + ...requiredProps, + parallelism: 0, + }); + }).toThrow(/must be at least 1/); + }); + + test('validating parallelismPerKpu', () => { + // Expect no error with valid value + new flink.Application(stack, 'ValidNumber', { + ...requiredProps, + parallelismPerKpu: 10, + }); + + // Expect no error with ref + new flink.Application(stack, 'ValidRef', { + ...requiredProps, + parallelismPerKpu: new core.CfnParameter(stack, 'Parameter', { + type: 'Number', + }).valueAsNumber, + }); + + expect(() => { + new flink.Application(stack, 'TooSmall', { + ...requiredProps, + parallelismPerKpu: 0, + }); + }).toThrow(/must be at least 1/); + }); + + test('fromFlinkApplicationName', () => { + const flinkApp = flink.Application.fromApplicationName(stack, 'Imported', 'my-app'); + + expect(flinkApp.applicationName).toEqual('my-app'); + expect(stack.resolve(flinkApp.applicationArn)).toEqual({ + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':kinesisanalytics:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':application/my-app', + ]], + }); + expect(flinkApp.addToRolePolicy(new iam.PolicyStatement())).toBe(false); + }); + + test('fromFlinkApplicationArn', () => { + const arn = 'arn:aws:kinesisanalytics:us-west-2:012345678901:application/my-app'; + const flinkApp = flink.Application.fromApplicationArn(stack, 'Imported', arn); + + expect(flinkApp.applicationName).toEqual('my-app'); + expect(flinkApp.applicationArn).toEqual(arn); + expect(flinkApp.addToRolePolicy(new iam.PolicyStatement())).toBe(false); + }); +}); diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/test/code-asset/WordCount.jar b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/code-asset/WordCount.jar new file mode 100644 index 0000000000000..9c533e6fea607 Binary files /dev/null and b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/code-asset/WordCount.jar differ diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application-code-from-bucket.lit.expected.json b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application-code-from-bucket.lit.expected.json new file mode 100644 index 0000000000000..6ab6a5f40bcab --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application-code-from-bucket.lit.expected.json @@ -0,0 +1,295 @@ +{ + "Parameters": { + "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67": { + "Type": "String", + "Description": "S3 bucket for asset \"8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577\"" + }, + "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E": { + "Type": "String", + "Description": "S3 key for asset version \"8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577\"" + }, + "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577ArtifactHash211A4F2F": { + "Type": "String", + "Description": "Artifact hash for asset \"8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577\"" + } + }, + "Resources": { + "AppRole1AF9B530": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "kinesisanalytics.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "AppRoleDefaultPolicy9CADBAA1": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudwatch:PutMetricData", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67" + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:*" + ] + ] + } + }, + { + "Action": "logs:DescribeLogStreams", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AppLogGroupC72EEC8C", + "Arn" + ] + } + }, + { + "Action": "logs:PutLogEvents", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "AppLogGroupC72EEC8C" + }, + ":log-stream:", + { + "Ref": "AppLogStream3CAF66A7" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AppRoleDefaultPolicy9CADBAA1", + "Roles": [ + { + "Ref": "AppRole1AF9B530" + } + ] + } + }, + "AppF1B96344": { + "Type": "AWS::KinesisAnalyticsV2::Application", + "Properties": { + "RuntimeEnvironment": "FLINK-1_11", + "ServiceExecutionRole": { + "Fn::GetAtt": [ + "AppRole1AF9B530", + "Arn" + ] + }, + "ApplicationConfiguration": { + "ApplicationCodeConfiguration": { + "CodeContent": { + "S3ContentLocation": { + "BucketARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67" + } + ] + ] + }, + "FileKey": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E" + } + ] + } + ] + } + ] + ] + } + } + }, + "CodeContentType": "ZIPFILE" + }, + "ApplicationSnapshotConfiguration": { + "SnapshotsEnabled": true + } + } + }, + "DependsOn": [ + "AppRoleDefaultPolicy9CADBAA1", + "AppRole1AF9B530" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AppLogGroupC72EEC8C": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "AppLogStream3CAF66A7": { + "Type": "AWS::Logs::LogStream", + "Properties": { + "LogGroupName": { + "Ref": "AppLogGroupC72EEC8C" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "AppLoggingOption75BE995E": { + "Type": "AWS::KinesisAnalyticsV2::ApplicationCloudWatchLoggingOption", + "Properties": { + "ApplicationName": { + "Ref": "AppF1B96344" + }, + "CloudWatchLoggingOption": { + "LogStreamARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "AppLogGroupC72EEC8C" + }, + ":log-stream:", + { + "Ref": "AppLogStream3CAF66A7" + } + ] + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application-code-from-bucket.lit.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application-code-from-bucket.lit.ts new file mode 100644 index 0000000000000..eb8c5ce3e9d03 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application-code-from-bucket.lit.ts @@ -0,0 +1,22 @@ +import * as path from 'path'; +import * as assets from '@aws-cdk/aws-s3-assets'; +import * as core from '@aws-cdk/core'; +import * as flink from '../lib'; + +const app = new core.App(); +const stack = new core.Stack(app, 'FlinkAppCodeFromBucketTest'); + +const asset = new assets.Asset(stack, 'CodeAsset', { + path: path.join(__dirname, 'code-asset'), +}); +const bucket = asset.bucket; +const fileKey = asset.s3ObjectKey; + +///! show +new flink.Application(stack, 'App', { + code: flink.ApplicationCode.fromBucket(bucket, fileKey), + runtime: flink.Runtime.FLINK_1_11, +}); +///! hide + +app.synth(); diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application.lit.expected.json b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application.lit.expected.json new file mode 100644 index 0000000000000..3b4f7ecf64f7e --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application.lit.expected.json @@ -0,0 +1,295 @@ +{ + "Resources": { + "AppRole1AF9B530": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "kinesisanalytics.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "AppRoleDefaultPolicy9CADBAA1": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudwatch:PutMetricData", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67" + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:*" + ] + ] + } + }, + { + "Action": "logs:DescribeLogStreams", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AppLogGroupC72EEC8C", + "Arn" + ] + } + }, + { + "Action": "logs:PutLogEvents", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "AppLogGroupC72EEC8C" + }, + ":log-stream:", + { + "Ref": "AppLogStream3CAF66A7" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AppRoleDefaultPolicy9CADBAA1", + "Roles": [ + { + "Ref": "AppRole1AF9B530" + } + ] + } + }, + "AppF1B96344": { + "Type": "AWS::KinesisAnalyticsV2::Application", + "Properties": { + "RuntimeEnvironment": "FLINK-1_11", + "ServiceExecutionRole": { + "Fn::GetAtt": [ + "AppRole1AF9B530", + "Arn" + ] + }, + "ApplicationConfiguration": { + "ApplicationCodeConfiguration": { + "CodeContent": { + "S3ContentLocation": { + "BucketARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67" + } + ] + ] + }, + "FileKey": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E" + } + ] + } + ] + } + ] + ] + } + } + }, + "CodeContentType": "ZIPFILE" + }, + "ApplicationSnapshotConfiguration": { + "SnapshotsEnabled": true + } + } + }, + "DependsOn": [ + "AppRoleDefaultPolicy9CADBAA1", + "AppRole1AF9B530" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AppLogGroupC72EEC8C": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "AppLogStream3CAF66A7": { + "Type": "AWS::Logs::LogStream", + "Properties": { + "LogGroupName": { + "Ref": "AppLogGroupC72EEC8C" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "AppLoggingOption75BE995E": { + "Type": "AWS::KinesisAnalyticsV2::ApplicationCloudWatchLoggingOption", + "Properties": { + "ApplicationName": { + "Ref": "AppF1B96344" + }, + "CloudWatchLoggingOption": { + "LogStreamARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "AppLogGroupC72EEC8C" + }, + ":log-stream:", + { + "Ref": "AppLogStream3CAF66A7" + } + ] + ] + } + } + } + } + }, + "Parameters": { + "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3BucketEBA17A67": { + "Type": "String", + "Description": "S3 bucket for asset \"8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577\"" + }, + "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577S3VersionKey5922697E": { + "Type": "String", + "Description": "S3 key for asset version \"8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577\"" + }, + "AssetParameters8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577ArtifactHash211A4F2F": { + "Type": "String", + "Description": "Artifact hash for asset \"8be9e0b5f53d41e9a3b1d51c9572c65f24f8170a7188d0ed57fb7d571de4d577\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application.lit.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application.lit.ts new file mode 100644 index 0000000000000..02a6a9949dcfa --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/test/integ.application.lit.ts @@ -0,0 +1,15 @@ +///! show +import * as path from 'path'; +import * as core from '@aws-cdk/core'; +import * as flink from '../lib'; + +const app = new core.App(); +const stack = new core.Stack(app, 'FlinkAppTest'); + +new flink.Application(stack, 'App', { + code: flink.ApplicationCode.fromAsset(path.join(__dirname, 'code-asset')), + runtime: flink.Runtime.FLINK_1_11, +}); +///! hide + +app.synth(); diff --git a/packages/@aws-cdk/aws-kinesisanalytics/README.md b/packages/@aws-cdk/aws-kinesisanalytics/README.md index e65b812cefc97..33dfe482719b0 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/README.md +++ b/packages/@aws-cdk/aws-kinesisanalytics/README.md @@ -14,3 +14,10 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +## Kinesis Analytics Flink + +The `aws-kinesisanalytics-flink` package provides constructs for building Flink applications. + + * [Github](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-kinesisanalytics-flink) + * [CDK Docs](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-kinesisanalytics-flink.html) diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 10849cc784a4a..fda319d7104db 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -202,6 +202,7 @@ "@aws-cdk/aws-kendra": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-kinesisanalytics": "0.0.0", + "@aws-cdk/aws-kinesisanalytics-flink": "0.0.0", "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lakeformation": "0.0.0", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index a5c184e65f1d5..e030318880208 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -128,6 +128,7 @@ "@aws-cdk/aws-kendra": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-kinesisanalytics": "0.0.0", + "@aws-cdk/aws-kinesisanalytics-flink": "0.0.0", "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lakeformation": "0.0.0", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index a02fee1b78939..f06d5a236d5e3 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -207,6 +207,7 @@ "@aws-cdk/aws-kendra": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-kinesisanalytics": "0.0.0", + "@aws-cdk/aws-kinesisanalytics-flink": "0.0.0", "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lakeformation": "0.0.0",