From d781f7da4c4957fd28c82f9b17b41582506fff50 Mon Sep 17 00:00:00 2001 From: Scott Hand Date: Mon, 2 Nov 2020 11:07:24 -0500 Subject: [PATCH 1/3] added AWS AppConfig hosted configuration with lambda using AppConfig lambda extension integration --- .../README.md | 58 +++++++++++++ .../cdk.json | 3 + .../index.ts | 87 +++++++++++++++++++ .../package.json | 28 ++++++ .../src/lambda-handler.ts | 67 ++++++++++++++ .../tsconfig.json | 20 +++++ 6 files changed, 263 insertions(+) create mode 100644 typescript/appconfig-hosted-configuration-lambda-extension/README.md create mode 100644 typescript/appconfig-hosted-configuration-lambda-extension/cdk.json create mode 100644 typescript/appconfig-hosted-configuration-lambda-extension/index.ts create mode 100644 typescript/appconfig-hosted-configuration-lambda-extension/package.json create mode 100644 typescript/appconfig-hosted-configuration-lambda-extension/src/lambda-handler.ts create mode 100644 typescript/appconfig-hosted-configuration-lambda-extension/tsconfig.json diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/README.md b/typescript/appconfig-hosted-configuration-lambda-extension/README.md new file mode 100644 index 000000000..766c18798 --- /dev/null +++ b/typescript/appconfig-hosted-configuration-lambda-extension/README.md @@ -0,0 +1,58 @@ +# AWS AppConfig Hosted Configuration with Lambda Extension + +--- + +![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge) + +> **This is a stable example. It should successfully build out of the box** +> +> This examples does is built on Construct Libraries marked "Stable" and does not have any infrastructure prerequisites to build. + +--- + + +This an example of AppConfig fature toggle use case with a hosted configuration and a consuming Lambda using AppConfig Lambda extension integration. + +## Build + +To build this app, you need to be in this example's root folder. Then run the following: + +```bash +npm install -g aws-cdk +npm install +npm run build +``` + +This will install the necessary CDK, then this example's dependencies, and then build your TypeScript files and your CloudFormation template. + +## Deploy + +Run `cdk deploy`. This will deploy / redeploy your Stack to your AWS Account. + +After the deployment you will see the API's URL, which represents the url you can then use. + +## The Component Structure + +The whole component contains: + +- An AppConfig Application, Environment, Hosted Configuration Profile and Deployment Strategy. +- Lambda pointing to `src/lambda-handler.ts`, containing code for __consuming__ AppConfig configuration data. + +## CDK Toolkit + +The [`cdk.json`](./cdk.json) file in the root of this repository includes +instructions for the CDK toolkit on how to execute this program. + +After building your TypeScript code, you will be able to run the CDK toolkits commands as usual: + + $ cdk ls + + + $ cdk synth + + + $ cdk deploy + + + $ cdk diff + diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/cdk.json b/typescript/appconfig-hosted-configuration-lambda-extension/cdk.json new file mode 100644 index 000000000..2f0e44c6f --- /dev/null +++ b/typescript/appconfig-hosted-configuration-lambda-extension/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "node index" +} diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/index.ts b/typescript/appconfig-hosted-configuration-lambda-extension/index.ts new file mode 100644 index 000000000..95a893623 --- /dev/null +++ b/typescript/appconfig-hosted-configuration-lambda-extension/index.ts @@ -0,0 +1,87 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnApplication, CfnEnvironment, CfnConfigurationProfile, CfnHostedConfigurationVersion, CfnDeploymentStrategy, CfnDeployment } from '@aws-cdk/aws-appconfig'; +import { Function, AssetCode, Runtime, LayerVersion } from '@aws-cdk/aws-lambda'; +import { Effect, PolicyStatement } from '@aws-cdk/aws-iam'; + +export class AppConfigHostedConfigurationStack extends cdk.Stack { + constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const application = new CfnApplication(this,'AppConfigApplication', { + name: 'AppConfigSampleApplication', + description: 'Sample AppConfig Application using CDK' + }); + + const environment = new CfnEnvironment(this, 'LambdaDevelopmentEnvironment', { + applicationId: application.ref, + name: 'AppConfigSampleLambdaDevelopmentEnvironment', + description: 'Sample AppConfig Development environment for Lambda implementation' + }); + + const configurationProfile = new CfnConfigurationProfile(this, 'ConfigurationProfile', { + applicationId: application.ref, + name: 'AppConfigSampleConfigurationProfile', + locationUri: 'hosted', + description: 'Sample AppConfig configuration profile' + }); + + const hostedConfigurationProfile = new CfnHostedConfigurationVersion(this, 'HostedConfigurationProfile', { + applicationId: application.ref, + configurationProfileId: configurationProfile.ref, + contentType: 'application/json', + content: '{\"boolEnableLimitResults\": true, \"intResultLimit\":5}', + latestVersionNumber: 1 + }); + + hostedConfigurationProfile.addMetadata('description', 'Sample AppConfig hosted configuration profile content'); + + const deploymentStrategy = new CfnDeploymentStrategy(this, 'DeploymentStrategy', { + name: 'Custom.AllAtOnce', + deploymentDurationInMinutes: 0, + growthFactor: 100, + finalBakeTimeInMinutes: 0, + replicateTo: 'NONE', + growthType: 'LINEAR', + description: 'Sample AppConfig deployment strategy - All at once deployment (i.e., immediate)' + }); + + const deployment = new CfnDeployment(this, 'Deployment', { + applicationId: application.ref, + configurationProfileId: configurationProfile.ref, + configurationVersion: '1', + deploymentStrategyId: deploymentStrategy.ref, + environmentId: environment.ref, + }); + + deployment.addMetadata('description', 'Sample AppConfig initial deployment'); + + const sampleAppConfigLambda = new Function(this, 'sampleAppConfigLambda', { + code: new AssetCode('src'), + functionName: 'SampleAppConfigLambda', + handler: 'lambda-handler.handler', + runtime: Runtime.NODEJS_12_X, + environment: { + AWS_APPCONFIG_EXTENSION_HTTP_PORT: '2772', + AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS: '45', + AWS_APPCONFIG_EXTENSION_POLL_TIMEOUT_MILLIS: '3000' + }, + layers: [ + LayerVersion.fromLayerVersionArn(this, 'AppConfigLambdaExtension', 'arn:aws:lambda:us-east-1:027255383542:layer:AWS-AppConfig-Extension:1') + ] + }); + + sampleAppConfigLambda.addToRolePolicy( + new PolicyStatement({ + resources: [ + `arn:aws:appconfig:${this.region}:${this.account}:application/${application.ref}*` + ], + actions: ['appconfig:GetConfiguration'], + effect: Effect.ALLOW + }) + ); + } +} + +const app = new cdk.App(); +new AppConfigHostedConfigurationStack(app, 'AppConfigHostedConfigurationStack'); +app.synth(); diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/package.json b/typescript/appconfig-hosted-configuration-lambda-extension/package.json new file mode 100644 index 000000000..4ff536ecf --- /dev/null +++ b/typescript/appconfig-hosted-configuration-lambda-extension/package.json @@ -0,0 +1,28 @@ +{ + "name": "appconfig-hosted-configuration", + "version": "1.0.0", + "description": "Use of AWS AppConfig with a hosted configuration", + "private": true, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "10.17.27", + "typescript": "~3.9.7" + }, + "dependencies": { + "@aws-cdk/aws-appconfig": "^1.71.0", + "@aws-cdk/aws-iam": "^1.71.0", + "@aws-cdk/aws-lambda": "^1.71.0", + "@aws-cdk/core": "^1.71.0", + "aws-sdk": "^2.783.0" + } +} diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/src/lambda-handler.ts b/typescript/appconfig-hosted-configuration-lambda-extension/src/lambda-handler.ts new file mode 100644 index 000000000..6b2e31a18 --- /dev/null +++ b/typescript/appconfig-hosted-configuration-lambda-extension/src/lambda-handler.ts @@ -0,0 +1,67 @@ +import * as http from 'http'; + + +export const handler = async (): Promise => { + // retrieve AppConfig configuration data from Lambda extension + const res: any = await new Promise((resolve) => { + http.get( + `http://localhost:2772/applications/AppConfigSampleApplication/environments/AppConfigSampleLambdaDevelopmentEnvironment/configurations/AppConfigSampleConfigurationProfile`, + resolve + ); + }); + + let configData: any = await new Promise((resolve, reject) => { + let data = ''; + res.on('data', (chunk: any) => data += chunk); + res.on('error', (err: any) => { + console.log(err); + reject(err); + }); + res.on('end', () => resolve(data)); + }); + + let result: {name: String}[] = getServices(); + const parsedConfigData = JSON.parse(configData); + + // implement feature toggle that filters results using configuration data + if ( (parsedConfigData.boolEnableLimitResults) && parsedConfigData.intResultLimit ) { + result = result.splice(0, parsedConfigData.intResultLimit); + } + + return result; +} + +const getServices = () => { + return [ + { + name: 'AWS AppConfig' + }, + { + name: 'Amazon SageMaker Studio' + }, + { + name: 'Amazon Kendra' + }, + { + name: 'Amazon CodeGuru' + }, + { + name: 'Amazon Fraud Detector' + }, + { + name: 'Amazon EKS on AWS Fargate' + }, + { + name: 'AWS Outposts' + }, + { + name: 'AWS Wavelength' + }, + { + name: 'AWS Transit Gateway' + }, + { + name: 'Amazon Detective' + } + ] +} \ No newline at end of file diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/tsconfig.json b/typescript/appconfig-hosted-configuration-lambda-extension/tsconfig.json new file mode 100644 index 000000000..588c16400 --- /dev/null +++ b/typescript/appconfig-hosted-configuration-lambda-extension/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target":"ES2018", + "module": "commonjs", + "lib": ["es2016", "es2017.object", "es2017.string"], + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization":false + } +} \ No newline at end of file From 469bb0a41d4e149c5961ef21b497f56e57f0bc3f Mon Sep 17 00:00:00 2001 From: Scott Hand Date: Mon, 2 Nov 2020 11:13:35 -0500 Subject: [PATCH 2/3] updated README to include appconfig example in list of Typescript examples --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fa28d35e3..ed3c1162d 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ $ cdk destroy | [custom-logical-names](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/custom-logical-names/) | Example of how to override logical name allocation | | [fargate-service-with-efs](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/ecs/fargate-service-with-efs/) | Starting a container fronted by an application load balancer on Fargate with an EFS Mount | | [http-proxy-apigateway](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/http-proxy-apigateway/) | Use ApiGateway to set up a http proxy | +| [appconfig-hosted-configuration-lambda-extension]() | Example of AWS AppConfig hosted configuration with consuming Lambda using AppConfig Lambda extension integration | ## Java examples From 4c67b1d0d9f874c18950a50d130c86285b2e4fcf Mon Sep 17 00:00:00 2001 From: Scott Hand Date: Mon, 2 Nov 2020 11:39:43 -0500 Subject: [PATCH 3/3] changed cdk versions to * --- .../index.ts | 2 ++ .../package.json | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/index.ts b/typescript/appconfig-hosted-configuration-lambda-extension/index.ts index 95a893623..76f54c64a 100644 --- a/typescript/appconfig-hosted-configuration-lambda-extension/index.ts +++ b/typescript/appconfig-hosted-configuration-lambda-extension/index.ts @@ -53,6 +53,8 @@ export class AppConfigHostedConfigurationStack extends cdk.Stack { environmentId: environment.ref, }); + deployment.addDependsOn(hostedConfigurationProfile); + deployment.addMetadata('description', 'Sample AppConfig initial deployment'); const sampleAppConfigLambda = new Function(this, 'sampleAppConfigLambda', { diff --git a/typescript/appconfig-hosted-configuration-lambda-extension/package.json b/typescript/appconfig-hosted-configuration-lambda-extension/package.json index 4ff536ecf..2b57146fe 100644 --- a/typescript/appconfig-hosted-configuration-lambda-extension/package.json +++ b/typescript/appconfig-hosted-configuration-lambda-extension/package.json @@ -19,10 +19,9 @@ "typescript": "~3.9.7" }, "dependencies": { - "@aws-cdk/aws-appconfig": "^1.71.0", - "@aws-cdk/aws-iam": "^1.71.0", - "@aws-cdk/aws-lambda": "^1.71.0", - "@aws-cdk/core": "^1.71.0", - "aws-sdk": "^2.783.0" + "@aws-cdk/aws-appconfig": "*", + "@aws-cdk/aws-iam": "*", + "@aws-cdk/aws-lambda": "*", + "@aws-cdk/core": "*" } }