diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.eslintignore
new file mode 100755
index 000000000..0819e2e65
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.eslintignore
@@ -0,0 +1,5 @@
+lib/*.js
+test/*.js
+*.d.ts
+coverage
+test/lambda/index.js
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.gitignore b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.gitignore
new file mode 100755
index 000000000..6773cabd2
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.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-lambda-sagemakerendpoint/.npmignore b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.npmignore
new file mode 100755
index 000000000..f66791629
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/.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-lambda-sagemakerendpoint/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/README.md
new file mode 100755
index 000000000..60f08141e
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/README.md
@@ -0,0 +1,125 @@
+# aws-lambda-sagemakerendpoint 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_lambda_sagemakerendpoint` |
+| ![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript | `@aws-solutions-constructs/aws-lambda-sagemakerendpoint` |
+| ![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java | `software.amazon.awsconstructs.services.lambdasagemakerendpoint` |
+
+This AWS Solutions Construct implements an AWS Lambda function connected to an Amazon Sagemaker Endpoint.
+
+Here is a minimal deployable pattern definition in Typescript:
+
+```typescript
+import { Duration } from '@aws-cdk/core';
+import * as lambda from '@aws-cdk/aws-lambda';
+import {
+ LambdaToSagemakerEndpoint,
+ LambdaToSagemakerEndpointProps,
+} from '@aws-solutions-constructs/aws-lambda-sagemakerendpoint';
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+};
+
+new LambdaToSagemakerEndpoint(this, 'LambdaToSagemakerEndpointPattern', constructProps);
+```
+
+## Initializer
+
+```text
+new LambdaToSagemakerEndpoint(scope: Construct, id: string, props: LambdaToSagemakerEndpointProps);
+```
+
+_Parameters_
+
+- scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html)
+- id `string`
+- props [`LambdaToSagemakerEndpointProps`](#pattern-construct-props)
+
+## Pattern Construct Props
+
+| **Name** | **Type** | **Description** |
+| :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| existingLambdaObj? | [`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html) | An optional, existing Lambda function to be used instead of the default function. If an existing function is provided, the `lambdaFunctionProps` property will be ignored. |
+| lambdaFunctionProps? | [`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html) | Optional user-provided properties to override the default properties for the Lambda function. Ignored if an `existingLambdaObj` is provided. | |
+| existingSagemakerEndpointObj? | [`sagemaker.CfnEndpoint`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnEndpoint.html) | An optional, existing Sagemaker Enpoint to be used. if this is set then the `modelProps?`, `endpointConfigProps?`, and `endpointProps?` are ignored |
+| modelProps? | [`sagemaker.CfnModelProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnModelProps.html) \| `any` | User-provided properties to override the default properties for the Sagemaker Model. At least `modelProps?.primaryContainer` must be provided to create a model. By default, the pattern will create a role with the minimum required permissions, but the client can provide a custom role with additional capabilities using `modelProps?.executionRoleArn`. `modelProps?` is ignored if `existingSagemakerEndpointObj?` is provided. |
+| endpointConfigProps? | [`sagemaker.CfnEndpointConfigProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnEndpointConfigProps.html) | Optional user-provided properties to override the default properties for the Sagemaker Endpoint Config. Ignored if `existingSagemakerEndpointObj?` is provided. |
+| endpointProps? | [`sagemaker.CfnEndpointProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnEndpointProps.html) | Optional user-provided properties to override the default properties for the Sagemaker Endpoint Config. Ignored if `existingSagemakerEndpointObj?` is provided. |
+| existingVpc? | `ec2.IVpc` | An optional, existing VPC into which this construct should be deployed. When deployed in a VPC, the Lambda function and Sagemaker Endpoint will use ENIs in the VPC to access network resources. An Interface Endpoint will be created in the VPC for Amazon Sagemaker Runtime, and Amazon S3 VPC Endpoint. If an existing VPC is provided, the `deployVpc?` property cannot be `true`. |
+| vpcProps? | `ec2.VpcProps` | Optional user-provided properties to override the default properties for the new VPC. `enableDnsHostnames`, `enableDnsSupport`, `natGateways` and `subnetConfiguration` are set by the Construct, so any values for those properties supplied here will be overrriden. If `deployVpc?` is not `true` then this property will be ignored. |
+| deployVpc? | `boolean` | Whether to create a new VPC based on defaults properties, or user-provided `vpcProps`, into which to deploy this construct. Setting this to `true` will deploy the minimal, most private VPC to run the construct. By default, the resources will be deployed in isolated subnets. If the `deployNatGateWay?` is set to `true`, resources will be deployed in private subnets with two NatGateWays deployed in the public subnets. One isolated, or private, subnet in each Availability Zone used by the CDK program. `enableDnsHostnames` and `enableDnsSupport` will both be set to true. If this property is `true` then `existingVpc?` cannot be specified. Defualts to `false`. | |
+| role? | `iam.Role` | Optional IAM role, with all required permissions, to be assumed by Sagemaker Service to create resources. The pattern creates an internal role with minimum required permissions. However, the customer can provided a custom role. If a role is provided, the `executionRoleArn` in [`sagemaker.CfnModelProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnModelProps.html) must be also specified. |
+
+## Pattern Properties
+
+| **Name** | **Type** | **Description** |
+| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
+| lambdaFunction | [`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html) | Returns an instance of the Lambda function created by the pattern. |
+| sagemakerEndpoint | [`sagemaker.CfnEndpoint`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnEndpoint.html) | Returns an instance of the Sagemaker Endpoint created by the pattern. |
+| sagemakerEndpointConfig? | [`sagemaker.CfnEndpointConfig`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnEndpointConfig.html) | Returns an instance of the SageMaker EndpointConfig created by the pattern, if `existingSagemakerEndpointObj?` is not provided. |
+| sagemakerModel? | [`sagemaker.CfnModel`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sagemaker.CfnModel.html) | Returns an instance of the Sagemaker Model created by the pattern, if `existingSagemakerEndpointObj?` is not provided. |
+| vpc? | `ec2.IVpc` | Returns an instance of the VPC created by the pattern, if `deployVpc?` is `true`, or `existingVpc?` is provided. |
+
+## Default settings
+
+Out of the box implementation of the Construct without any override will set the following defaults:
+
+### AWS Lambda Function
+
+- Configure limited privilege access IAM role for Lambda function
+- Enable reusing connections with Keep-Alive for NodeJs Lambda function
+- Allow the function to invoke the Sagemaker endpoint for Inferences
+- Configure the function to access resources in the VPC, where the Sagemaker endpoint is deployed
+- Enable X-Ray Tracing
+- Set environment variables:
+ - SAGEMAKER_ENDPOINT_NAME
+ - AWS_NODEJS_CONNECTION_REUSE_ENABLED (for Node 10.x and higher functions).
+
+### Amazon Sagemaker Endpoint
+
+- Configure limited privilege to create Sagemaker resources
+- Deploy Sagemaker model, endpointConfig, and endpoint
+- Configure the Sagemaker endpoint to be deployed in a VPC
+- Deploy S3 VPC Endpoint and Sagemaker Runtime VPC Interface
+
+## 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-lambda-sagemakerendpoint/architecture.png b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/architecture.png
new file mode 100644
index 000000000..e739feff8
Binary files /dev/null and b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/architecture.png differ
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/lib/index.ts
new file mode 100644
index 000000000..182828abf
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/lib/index.ts
@@ -0,0 +1,151 @@
+/**
+ * Copyright 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. 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.
+ */
+
+import * as cdk from '@aws-cdk/core';
+import * as lambda from '@aws-cdk/aws-lambda';
+import * as ec2 from '@aws-cdk/aws-ec2';
+import * as iam from '@aws-cdk/aws-iam';
+import * as sagemaker from '@aws-cdk/aws-sagemaker';
+import * as defaults from '@aws-solutions-constructs/core';
+
+/**
+ * @summary The properties for the LambdaToSagemakerEndpoint class
+ */
+export interface LambdaToSagemakerEndpointProps {
+ /**
+ * Existing instance of Lambda Function object, if this is set then the lambdaFunctionProps is ignored
+ *
+ * @default - None
+ */
+ readonly existingLambdaObj?: lambda.Function;
+ /**
+ * User provided props to override the default props for the Lambda function
+ *
+ * @default - Default props are used
+ */
+ readonly lambdaFunctionProps?: lambda.FunctionProps;
+ /**
+ * Existing Sagemaker Enpoint object, if this is set then the modelProps, endpointConfigProps, and endpointProps are ignored
+ *
+ * @default - None
+ */
+ readonly existingSagemakerEndpointObj?: sagemaker.CfnEndpoint;
+ /**
+ * User provided props to create Sagemaker Model
+ *
+ * @default - None
+ */
+ readonly modelProps?: sagemaker.CfnModelProps | any;
+ /**
+ * User provided props to create Sagemaker Endpoint Configuration
+ *
+ * @default - Default props are used
+ */
+ readonly endpointConfigProps?: sagemaker.CfnEndpointConfigProps;
+ /**
+ * User provided props to create Sagemaker Endpoint
+ *
+ * @default - Default props are used
+ */
+ readonly endpointProps?: sagemaker.CfnEndpointProps;
+ /**
+ * An existing VPC for the construct to use (construct will NOT create a new VPC in this case)
+ *
+ * @default - None
+ */
+ readonly existingVpc?: ec2.IVpc;
+ /**
+ * Properties to override default properties if deployVpc is true
+ *
+ * @default - None
+ */
+ readonly vpcProps?: ec2.VpcProps;
+ /**
+ * Whether to deploy a new VPC
+ *
+ * @default - false
+ */
+ readonly deployVpc?: boolean;
+}
+
+/**
+ * @summary The LambdaToSagemakerEndpoint class.
+ */
+export class LambdaToSagemakerEndpoint extends cdk.Construct {
+ public readonly lambdaFunction: lambda.Function;
+ public readonly sagemakerEndpoint: sagemaker.CfnEndpoint;
+ public readonly sagemakerEndpointConfig?: sagemaker.CfnEndpointConfig;
+ public readonly sagemakerModel?: sagemaker.CfnModel;
+ public readonly vpc?: ec2.IVpc;
+
+ /**
+ * @summary Constructs a new instance of the LambdaToSagemakerEndpoint class.
+ * @param {cdk.App} scope - represents the scope for all the resources.
+ * @param {string} id - this is a scope-unique id.
+ * @param {LambdaToSagemakerEndpointProps} props - user provided props for the construct.
+ * @since 1.87.1
+ * @access public
+ */
+ constructor(scope: cdk.Construct, id: string, props: LambdaToSagemakerEndpointProps) {
+ super(scope, id);
+
+ if (props.deployVpc || props.existingVpc) {
+ if (props.deployVpc && props.existingVpc) {
+ throw new Error('More than 1 VPC specified in the properties');
+ }
+
+ // create the VPC
+ this.vpc = defaults.buildVpc(scope, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ existingVpc: props.existingVpc,
+ userVpcProps: props.vpcProps,
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ // Add S3 VPC Gateway Endpoint, required by Sagemaker to access Models artifacts via AWS private network
+ defaults.AddAwsServiceEndpoint(scope, this.vpc, defaults.ServiceEndpointTypes.S3);
+ // Add SAGEMAKER_RUNTIME VPC Interface Endpoint, required by the lambda function to invoke the Sagemaker endpoint
+ defaults.AddAwsServiceEndpoint(scope, this.vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+ }
+
+ // Build Sagemaker Endpoint (inclduing Sagemaker's Endpoint Configuration and Model)
+ [this.sagemakerEndpoint, this.sagemakerEndpointConfig, this.sagemakerModel] = defaults.BuildSagemakerEndpoint(
+ this,
+ {
+ ...props,
+ vpc: this.vpc,
+ }
+ );
+
+ // Setup the Lambda function
+ this.lambdaFunction = defaults.buildLambdaFunction(this, {
+ existingLambdaObj: props.existingLambdaObj,
+ lambdaFunctionProps: props.lambdaFunctionProps,
+ vpc: this.vpc,
+ });
+
+ // Add SAGEMAKER_ENDPOINT_NAME environment variable
+ this.lambdaFunction.addEnvironment('SAGEMAKER_ENDPOINT_NAME', this.sagemakerEndpoint.attrEndpointName);
+
+ // Add permission to invoke the SageMaker endpoint
+ this.lambdaFunction.addToRolePolicy(
+ new iam.PolicyStatement({
+ actions: ['sagemaker:InvokeEndpoint'],
+ resources: [this.sagemakerEndpoint.ref],
+ })
+ );
+ }
+}
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/package.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/package.json
new file mode 100644
index 000000000..d3cf85160
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/package.json
@@ -0,0 +1,83 @@
+{
+ "name": "@aws-solutions-constructs/aws-lambda-sagemakerendpoint",
+ "version": "0.0.0",
+ "description": "CDK constructs for defining an interaction between an AWS Lambda function and an Amazon SageMaker inference endpoint.",
+ "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-lambda-sagemakerendpoint"
+ },
+ "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.lambdasagemakerendpoint",
+ "maven": {
+ "groupId": "software.amazon.awsconstructs",
+ "artifactId": "lambdasqs"
+ }
+ },
+ "dotnet": {
+ "namespace": "Amazon.Constructs.AWS.LambdaSagemakerEndpoint",
+ "packageId": "Amazon.Constructs.AWS.LambdaSagemakerEndpoint",
+ "signAssembly": true,
+ "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png"
+ },
+ "python": {
+ "distName": "aws-solutions-constructs.aws-lambda-sagemakerendpoint",
+ "module": "aws_solutions_constructs.aws_lambda_sagemakerendpoint"
+ }
+ }
+ },
+ "dependencies": {
+ "@aws-cdk/aws-ec2": "0.0.0",
+ "@aws-cdk/aws-iam": "0.0.0",
+ "@aws-cdk/aws-lambda": "0.0.0",
+ "@aws-cdk/aws-sagemaker": "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": "^24.0.23",
+ "@types/node": "^10.3.0"
+ },
+ "jest": {
+ "moduleFileExtensions": [
+ "js"
+ ]
+ },
+ "peerDependencies": {
+ "@aws-cdk/aws-ec2": "0.0.0",
+ "@aws-cdk/aws-iam": "0.0.0",
+ "@aws-cdk/aws-lambda": "0.0.0",
+ "@aws-cdk/aws-sagemaker": "0.0.0",
+ "@aws-cdk/core": "0.0.0",
+ "@aws-solutions-constructs/core": "0.0.0",
+ "constructs": "^3.2.0"
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/__snapshots__/aws-lambda-sagemakerendpoint.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/__snapshots__/aws-lambda-sagemakerendpoint.test.js.snap
new file mode 100644
index 000000000..a19dbae99
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/__snapshots__/aws-lambda-sagemakerendpoint.test.js.snap
@@ -0,0 +1,1600 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Pattern deployment with existing Lambda function, new Sagemaker endpoint, deployVpc = false 1`] = `
+Object {
+ "Parameters": Object {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": Object {
+ "Description": "Artifact hash for asset \\"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\\"",
+ "Type": "String",
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": Object {
+ "Description": "S3 bucket for asset \\"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\\"",
+ "Type": "String",
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": Object {
+ "Description": "S3 key for asset version \\"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\\"",
+ "Type": "String",
+ },
+ },
+ "Resources": Object {
+ "LambdaFunctionBF21E41F": Object {
+ "DependsOn": Array [
+ "LambdaFunctionServiceRoleDefaultPolicy126C8897",
+ "LambdaFunctionServiceRole0C4CDE0B",
+ ],
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions.",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "Code": Object {
+ "S3Bucket": Object {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499",
+ },
+ "S3Key": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::Split": Array [
+ "||",
+ Object {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349",
+ },
+ ],
+ },
+ ],
+ },
+ Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::Split": Array [
+ "||",
+ Object {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ ],
+ },
+ },
+ "Environment": Object {
+ "Variables": Object {
+ "SAGEMAKER_ENDPOINT_NAME": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName",
+ ],
+ },
+ },
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Role": Object {
+ "Fn::GetAtt": Array [
+ "LambdaFunctionServiceRole0C4CDE0B",
+ "Arn",
+ ],
+ },
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": Object {
+ "Mode": "Active",
+ },
+ },
+ "Type": "AWS::Lambda::Function",
+ },
+ "LambdaFunctionServiceRole0C4CDE0B": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "lambda.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "Policies": Array [
+ Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/lambda/*",
+ ],
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC.",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords",
+ ],
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897",
+ "Roles": Array [
+ Object {
+ "Ref": "LambdaFunctionServiceRole0C4CDE0B",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName",
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Endpoint",
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ ],
+ "Properties": Object {
+ "KmsKeyId": Object {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0",
+ },
+ "ProductionVariants": Array [
+ Object {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName",
+ ],
+ },
+ "VariantName": "AllTraffic",
+ },
+ ],
+ },
+ "Type": "AWS::SageMaker::EndpointConfig",
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ ],
+ "Properties": Object {
+ "ExecutionRoleArn": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn",
+ ],
+ },
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
+ },
+ },
+ "Type": "AWS::SageMaker::Model",
+ },
+ "testlambdasagemakerSagemakerRoleD84546B8": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "Roles": Array [
+ Object {
+ "Ref": "testlambdasagemakerSagemakerRoleD84546B8",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ },
+}
+`;
+
+exports[`Pattern deployment with new Lambda function, new Sagemaker endpoint, deployVpc = true 1`] = `
+Object {
+ "Parameters": Object {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": Object {
+ "Description": "Artifact hash for asset \\"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\\"",
+ "Type": "String",
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": Object {
+ "Description": "S3 bucket for asset \\"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\\"",
+ "Type": "String",
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": Object {
+ "Description": "S3 key for asset version \\"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\\"",
+ "Type": "String",
+ },
+ },
+ "Resources": Object {
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/ReplaceEndpointDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "SecurityGroupIngress": Array [
+ Object {
+ "CidrIp": Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ "Description": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "from ",
+ Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ ":443",
+ ],
+ ],
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "Vpc8378EB38": Object {
+ "Properties": Object {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::VPC",
+ },
+ "VpcFlowLog8FF33A73": Object {
+ "Properties": Object {
+ "DeliverLogsPermissionArn": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": Object {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9",
+ },
+ "ResourceId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ "ResourceType": "VPC",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ "TrafficType": "ALL",
+ },
+ "Type": "AWS::EC2::FlowLog",
+ },
+ "VpcFlowLogIAMRole6A475D41": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "vpc-flow-logs.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": Object {
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": Array [
+ Object {
+ "Ref": "VpcFlowLogIAMRole6A475D41",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "VpcFlowLogLogGroup7B5C56B9": Object {
+ "DeletionPolicy": "Retain",
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "RetentionInDays": 731,
+ },
+ "Type": "AWS::Logs::LogGroup",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "VpcS3A5408339": Object {
+ "Properties": Object {
+ "RouteTableIds": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764",
+ },
+ ],
+ "ServiceName": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "com.amazonaws.",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".s3",
+ ],
+ ],
+ },
+ "VpcEndpointType": "Gateway",
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::VPCEndpoint",
+ },
+ "VpcSAGEMAKERRUNTIME337E125A": Object {
+ "Properties": Object {
+ "PrivateDnsEnabled": true,
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF",
+ "GroupId",
+ ],
+ },
+ ],
+ "ServiceName": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "com.amazonaws.",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".sagemaker.runtime",
+ ],
+ ],
+ },
+ "SubnetIds": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
+ },
+ ],
+ "VpcEndpointType": "Interface",
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::VPCEndpoint",
+ },
+ "VpcisolatedSubnet1RouteTableAssociationD259E31A": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcisolatedSubnet1RouteTableE442650B": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/isolatedSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcisolatedSubnet1SubnetE62B1B9B": Object {
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.0.0/18",
+ "MapPublicIpOnLaunch": false,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/isolatedSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcisolatedSubnet2RouteTable334F9764": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/isolatedSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcisolatedSubnet2RouteTableAssociation25A4716F": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcisolatedSubnet2Subnet39217055": Object {
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.64.0/18",
+ "MapPublicIpOnLaunch": false,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/isolatedSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "testlambdasagemakerLambdaFunction661E043F": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ ],
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions.",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "Code": Object {
+ "S3Bucket": Object {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499",
+ },
+ "S3Key": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::Split": Array [
+ "||",
+ Object {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349",
+ },
+ ],
+ },
+ ],
+ },
+ Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::Split": Array [
+ "||",
+ Object {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ ],
+ },
+ },
+ "Environment": Object {
+ "Variables": Object {
+ "SAGEMAKER_ENDPOINT_NAME": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName",
+ ],
+ },
+ },
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Role": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ "Arn",
+ ],
+ },
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": Object {
+ "Mode": "Active",
+ },
+ "VpcConfig": Object {
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810",
+ "GroupId",
+ ],
+ },
+ ],
+ "SubnetIds": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
+ },
+ ],
+ },
+ },
+ "Type": "AWS::Lambda::Function",
+ },
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "lambda.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "Policies": Array [
+ Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/lambda/*",
+ ],
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC.",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "ec2:CreateNetworkInterface",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:DeleteNetworkInterface",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses",
+ ],
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords",
+ ],
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "Roles": Array [
+ Object {
+ "Ref": "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/test-lambda-sagemaker/ReplaceDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/test-lambda-sagemaker/ReplaceModelDefaultSecurityGroup",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "SecurityGroupIngress": Array [
+ Object {
+ "CidrIp": Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ "Description": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "from ",
+ Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ ":443",
+ ],
+ ],
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName",
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Endpoint",
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ ],
+ "Properties": Object {
+ "KmsKeyId": Object {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0",
+ },
+ "ProductionVariants": Array [
+ Object {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName",
+ ],
+ },
+ "VariantName": "AllTraffic",
+ },
+ ],
+ },
+ "Type": "AWS::SageMaker::EndpointConfig",
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": Object {
+ "DependsOn": Array [
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ ],
+ "Properties": Object {
+ "ExecutionRoleArn": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn",
+ ],
+ },
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
+ },
+ "VpcConfig": Object {
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24",
+ "GroupId",
+ ],
+ },
+ ],
+ "Subnets": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
+ },
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Model",
+ },
+ "testlambdasagemakerSagemakerRoleD84546B8": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ec2:CreateNetworkInterface",
+ "ec2:CreateNetworkInterfacePermission",
+ "ec2:DeleteNetworkInterface",
+ "ec2:DeleteNetworkInterfacePermission",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses",
+ "ec2:DescribeVpcs",
+ "ec2:DescribeDhcpOptions",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeSecurityGroups",
+ ],
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "Roles": Array [
+ Object {
+ "Ref": "testlambdasagemakerSagemakerRoleD84546B8",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ },
+}
+`;
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/aws-lambda-sagemakerendpoint.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/aws-lambda-sagemakerendpoint.test.ts
new file mode 100644
index 000000000..206b361da
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/aws-lambda-sagemakerendpoint.test.ts
@@ -0,0 +1,545 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as defaults from '@aws-solutions-constructs/core';
+import * as lambda from '@aws-cdk/aws-lambda';
+import * as iam from '@aws-cdk/aws-iam';
+import { SynthUtils } from '@aws-cdk/assert';
+import '@aws-cdk/assert/jest';
+
+// -----------------------------------------------------------------------------------------
+// Pattern deployment with new Lambda function, new Sagemaker endpoint and deployVpc = true
+// -----------------------------------------------------------------------------------------
+test('Pattern deployment with new Lambda function, new Sagemaker endpoint, deployVpc = true', () => {
+ // Initial Setup
+ const stack = new Stack();
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+ deployVpc: true,
+ };
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ // Assertion 1
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+ // Assertion 2
+ expect(stack).toHaveResourceLike('AWS::Lambda::Function', {
+ Environment: {
+ Variables: {
+ SAGEMAKER_ENDPOINT_NAME: {
+ 'Fn::GetAtt': ['testlambdasagemakerSagemakerEndpoint12803730', 'EndpointName'],
+ },
+ },
+ },
+ VpcConfig: {
+ SecurityGroupIds: [
+ {
+ 'Fn::GetAtt': ['testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810', 'GroupId'],
+ },
+ ],
+ SubnetIds: [
+ {
+ Ref: 'VpcisolatedSubnet1SubnetE62B1B9B',
+ },
+ {
+ Ref: 'VpcisolatedSubnet2Subnet39217055',
+ },
+ ],
+ },
+ });
+ // Assertion 3
+ expect(stack).toHaveResourceLike('AWS::SageMaker::Model', {
+ ExecutionRoleArn: {
+ 'Fn::GetAtt': ['testlambdasagemakerSagemakerRoleD84546B8', 'Arn'],
+ },
+ PrimaryContainer: {
+ Image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ ModelDataUrl: 's3:////model.tar.gz',
+ },
+ VpcConfig: {
+ SecurityGroupIds: [
+ {
+ 'Fn::GetAtt': ['testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24', 'GroupId'],
+ },
+ ],
+ Subnets: [
+ {
+ Ref: 'VpcisolatedSubnet1SubnetE62B1B9B',
+ },
+ {
+ Ref: 'VpcisolatedSubnet2Subnet39217055',
+ },
+ ],
+ },
+ });
+
+ // Assertion 4
+ expect(stack).toHaveResourceLike('AWS::SageMaker::EndpointConfig', {
+ ProductionVariants: [
+ {
+ InitialInstanceCount: 1,
+ InitialVariantWeight: 1,
+ InstanceType: 'ml.m4.xlarge',
+ ModelName: {
+ 'Fn::GetAtt': ['testlambdasagemakerSagemakerModelEC3E4E39', 'ModelName'],
+ },
+ VariantName: 'AllTraffic',
+ },
+ ],
+ KmsKeyId: {
+ Ref: 'testlambdasagemakerEncryptionKey2AACF9E0',
+ },
+ });
+
+ // Assertion 5
+ expect(stack).toHaveResourceLike('AWS::SageMaker::Endpoint', {
+ EndpointConfigName: {
+ 'Fn::GetAtt': ['testlambdasagemakerSagemakerEndpointConfig6BABA334', 'EndpointConfigName'],
+ },
+ });
+});
+
+// ----------------------------------------------------------------------------------------------
+// Pattern deployment with existing Lambda function, new Sagemaker endpoint and deployVpc = false
+// ----------------------------------------------------------------------------------------------
+test('Pattern deployment with existing Lambda function, new Sagemaker endpoint, deployVpc = false', () => {
+ // Initial Setup
+ const stack = new Stack();
+ // deploy lambda function
+ const fn = defaults.deployLambdaFunction(stack, {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ existingLambdaObj: fn,
+ };
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ // Assertion 1
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+
+ // Assertion 2
+ expect(stack).toHaveResourceLike('AWS::SageMaker::Model', {
+ ExecutionRoleArn: {
+ 'Fn::GetAtt': ['testlambdasagemakerSagemakerRoleD84546B8', 'Arn'],
+ },
+ PrimaryContainer: {
+ Image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ ModelDataUrl: 's3:////model.tar.gz',
+ },
+ });
+
+ // Assertion 3
+ expect(stack).toHaveResourceLike('AWS::Lambda::Function', {
+ Environment: {
+ Variables: {
+ SAGEMAKER_ENDPOINT_NAME: {
+ 'Fn::GetAtt': ['testlambdasagemakerSagemakerEndpoint12803730', 'EndpointName'],
+ },
+ },
+ },
+ });
+});
+
+// ------------------------------------------------------------------------------------------------------------------
+// Pattern deployment with new Lambda function, new Sagemaker endpoint, deployVpc = true, and custom role
+// ------------------------------------------------------------------------------------------------------------------
+test('Pattern deployment with new Lambda function, new Sagemaker endpoint, deployVpc = true, and custom role', () => {
+ // Initial Setup
+ const stack = new Stack();
+ // Create IAM Role to be assumed by SageMaker
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
+ });
+ sagemakerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSageMakerFullAccess'));
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ executionRoleArn: sagemakerRole.roleArn,
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ deployVpc: true,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+ };
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ // Assertion 1
+ expect(stack).toHaveResourceLike('AWS::IAM::Role', {
+ AssumeRolePolicyDocument: {
+ Statement: [
+ {
+ Action: 'sts:AssumeRole',
+ Effect: 'Allow',
+ Principal: {
+ Service: 'sagemaker.amazonaws.com',
+ },
+ },
+ ],
+ Version: '2012-10-17',
+ },
+ });
+
+ // Assertion 2: ReplaceDefaultSecurityGroup, ReplaceEndpointDefaultSecurityGroup, and ReplaceModelDefaultSecurityGroup
+ expect(stack).toCountResources('AWS::EC2::SecurityGroup', 3);
+ // Assertion 3
+ expect(stack).toCountResources('AWS::EC2::Subnet', 2);
+ // Assertion 4
+ expect(stack).toCountResources('AWS::EC2::InternetGateway', 0);
+ // Assertion 5: SAGEMAKER_RUNTIME VPC Interface
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Interface',
+ });
+ // Assertion 6: S3 VPC Endpoint
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Gateway',
+ });
+ // Assertion 7
+ expect(stack).toHaveResource('AWS::EC2::VPC', {
+ EnableDnsHostnames: true,
+ EnableDnsSupport: true,
+ });
+});
+
+// ---------------------------------------------------------------------------------
+// Test for error when existing Lambda function does not have vpc and deployVpc = true
+// ---------------------------------------------------------------------------------
+test('Test for errot when existing Lambda function does not have vpc and deployVpc = true ', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ // deploy lambda function
+ const fn = defaults.deployLambdaFunction(stack, {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ deployVpc: true,
+ existingLambdaObj: fn,
+ };
+
+ const app = () => {
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// -------------------------------------------------------------------------------------------------------
+// Pattern deployment with existing Lambda function (with VPC), new Sagemaker endpoint, and existingVpc
+// -------------------------------------------------------------------------------------------------------
+test('Pattern deployment with existing Lambda function (with VPC), new Sagemaker endpoint, and existingVpc', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ // Add S3 VPC Gateway Endpint, required by Sagemaker to access Models artifacts via AWS private network
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.S3);
+ // Add SAGEMAKER_RUNTIME VPC Interface Endpint, required by the lambda function to invoke the SageMaker endpoint
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+ // deploy lambda function
+ const fn = defaults.deployLambdaFunction(stack, {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ vpc,
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ existingVpc: vpc,
+ existingLambdaObj: fn,
+ };
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+ // Assertion 2: ReplaceDefaultSecurityGroup, ReplaceEndpointDefaultSecurityGroup, and ReplaceModelDefaultSecurityGroup
+ expect(stack).toCountResources('AWS::EC2::SecurityGroup', 3);
+ // Assertion 3
+ expect(stack).toCountResources('AWS::EC2::Subnet', 2);
+ // Assertion 4
+ expect(stack).toCountResources('AWS::EC2::InternetGateway', 0);
+ // Assertion 5: SAGEMAKER_RUNTIME VPC Interface
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Interface',
+ });
+ // Assertion 6: S3 VPC Endpoint
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Gateway',
+ });
+ // Assertion 7
+ expect(stack).toHaveResource('AWS::EC2::VPC', {
+ EnableDnsHostnames: true,
+ EnableDnsSupport: true,
+ });
+});
+
+// -----------------------------------------------------------------------------------------
+// Test for error with existingLambdaObj/lambdaFunctionProps=undefined (not supplied by user)
+// -----------------------------------------------------------------------------------------
+test('Test for error with existingLambdaObj/lambdaFunctionProps=undefined (not supplied by user)', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ const props: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ };
+ const app = () => {
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', props);
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// --------------------------------------------------------------------
+// Test for error with (props.deployVpc && props.existingVpc) is true
+// --------------------------------------------------------------------
+test('Test for error with (props.deployVpc && props.existingVpc) is true', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ // Add S3 VPC Gateway Endpint, required by Sagemaker to access Models artifacts via AWS private network
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.S3);
+ // Add SAGEMAKER_RUNTIME VPC Interface Endpint, required by the lambda function to invoke the SageMaker endpoint
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ deployVpc: true,
+ existingVpc: vpc,
+ };
+ const app = () => {
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// ----------------------------------------------------------------------------------------------------------
+// Test for error with primaryContainer=undefined (not supplied by user), and no existingSageMakerEndpointObj
+// ----------------------------------------------------------------------------------------------------------
+test('Test for error with primaryContainer=undefined (not supplied by user)', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ // deploy lambda function
+ const fn = defaults.deployLambdaFunction(stack, {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {},
+ deployVpc: true,
+ existingLambdaObj: fn,
+ };
+ const app = () => {
+ new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// -------------------------------------------------------------------------------------------------
+// Test getter methods: existing Lambda function (with VPC), new Sagemaker endpoint, and existingVpc
+// -------------------------------------------------------------------------------------------------
+test('Test getter methods: existing Lambda function (with VPC), new Sagemaker endpoint, and existingVpc', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ // Add S3 VPC Gateway Endpint, required by Sagemaker to access Models artifacts via AWS private network
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.S3);
+ // Add SAGEMAKER_RUNTIME VPC Interface Endpint, required by the lambda function to invoke the SageMaker endpoint
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+ // deploy lambda function
+ const fn = defaults.deployLambdaFunction(stack, {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ vpc,
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ existingVpc: vpc,
+ existingLambdaObj: fn,
+ };
+ const app = new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ // Assertions
+ expect(app.lambdaFunction !== null);
+ expect(app.sagemakerEndpoint !== null);
+ expect(app.sagemakerEndpointConfig !== null);
+ expect(app.sagemakerModel !== null);
+ expect(app.vpc !== null);
+});
+
+// --------------------------------------------------------------------------------------------
+// Test getter methods: new Lambda function, existingSagemakerendpointObj (no vpc)
+// --------------------------------------------------------------------------------------------
+test('Test getter methods: new Lambda function, existingSagemakerendpointObj (no vpc)', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ const [sagemakerEndpoint] = defaults.deploySagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ existingSagemakerEndpointObj: sagemakerEndpoint,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+ };
+ const app = new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ // Assertions
+ expect(app.lambdaFunction !== null);
+ expect(app.sagemakerEndpoint !== null);
+ expect(app.sagemakerEndpointConfig === null);
+ expect(app.sagemakerModel === null);
+ expect(app.vpc === null);
+});
+
+// --------------------------------------------------------------------------------------------
+// Test getter methods: new Lambda function, existingSagemakerendpointObj and deployVpc = true
+// --------------------------------------------------------------------------------------------
+test('Test getter methods: new Lambda function, existingSagemakerendpointObj and deployVpc = true', () => {
+ // Initial Setup
+ const stack = new Stack();
+
+ const [sagemakerEndpoint] = defaults.deploySagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ });
+
+ const constructProps: LambdaToSagemakerEndpointProps = {
+ existingSagemakerEndpointObj: sagemakerEndpoint,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+ deployVpc: true,
+ };
+ const app = new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+ // Assertions
+ expect(app.lambdaFunction !== null);
+ expect(app.sagemakerEndpoint !== null);
+ expect(app.sagemakerEndpointConfig === null);
+ expect(app.sagemakerModel === null);
+ expect(app.vpc !== null);
+});
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunction.expected.json
new file mode 100644
index 000000000..272d7fe27
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunction.expected.json
@@ -0,0 +1,541 @@
+{
+ "Description": "Integration Test for aws-lambda-sagemakerendpoint",
+ "Resources": {
+ "testlambdasagemakerSagemakerRoleD84546B8": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "sagemaker.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":sagemaker:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/sagemaker/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":ecr:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":repository/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":key/*"
+ ]
+ ]
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":alias/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*"
+ },
+ {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringLike": {
+ "iam:PassedToService": "sagemaker.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerSagemakerRoleD84546B8"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference."
+ },
+ {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": {
+ "Type": "AWS::SageMaker::Model",
+ "Properties": {
+ "ExecutionRoleArn": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ },
+ "PrimaryContainer": {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "testlambdasagemakerSagemakerRoleD84546B8"
+ ]
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": {
+ "Type": "AWS::KMS::Key",
+ "Properties": {
+ "KeyPolicy": {
+ "Statement": [
+ {
+ "Action": [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource"
+ ],
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":root"
+ ]
+ ]
+ }
+ },
+ "Resource": "*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "EnableKeyRotation": true
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain"
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": {
+ "Type": "AWS::SageMaker::EndpointConfig",
+ "Properties": {
+ "ProductionVariants": [
+ {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName"
+ ]
+ },
+ "VariantName": "AllTraffic"
+ }
+ ],
+ "KmsKeyId": {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerModelEC3E4E39"
+ ]
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": {
+ "Type": "AWS::SageMaker::Endpoint",
+ "Properties": {
+ "EndpointConfigName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName"
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334"
+ ]
+ },
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Policies": [
+ {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/lambda/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy"
+ }
+ ]
+ }
+ },
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC."
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerLambdaFunction661E043F": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Code": {
+ "S3Bucket": {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499"
+ },
+ "S3Key": {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::Select": [
+ 0,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Fn::Select": [
+ 1,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "Role": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ "Arn"
+ ]
+ },
+ "Environment": {
+ "Variables": {
+ "SAGEMAKER_ENDPOINT_NAME": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName"
+ ]
+ }
+ }
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": {
+ "Mode": "Active"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ ],
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions."
+ }
+ ]
+ }
+ }
+ }
+ },
+ "Parameters": {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": {
+ "Type": "String",
+ "Description": "S3 bucket for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": {
+ "Type": "String",
+ "Description": "S3 key for asset version \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": {
+ "Type": "String",
+ "Description": "Artifact hash for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunction.ts
new file mode 100644
index 000000000..5af13102e
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunction.ts
@@ -0,0 +1,43 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration, App } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as lambda from '@aws-cdk/aws-lambda';
+
+// Setup
+const app = new App();
+const stack = new Stack(app, 'test-lambda-sagemakerendpoint');
+stack.templateOptions.description = 'Integration Test for aws-lambda-sagemakerendpoint';
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+};
+
+new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+// Synth
+app.synth();
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVPCandCustomRole.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVPCandCustomRole.expected.json
new file mode 100644
index 000000000..2b89742d8
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVPCandCustomRole.expected.json
@@ -0,0 +1,876 @@
+{
+ "Description": "Integration Test for aws-lambda-sagemakerendpoint",
+ "Resources": {
+ "SagemakerRole5FDB64E1": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "sagemaker.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "ManagedPolicyArns": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::aws:policy/AmazonSageMakerFullAccess"
+ ]
+ ]
+ }
+ ]
+ }
+ },
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/test-lambda-sagemaker/ReplaceModelDefaultSecurityGroup",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "SecurityGroupIngress": [
+ {
+ "CidrIp": {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ "Description": {
+ "Fn::Join": [
+ "",
+ [
+ "from ",
+ {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ ":443"
+ ]
+ ]
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": {
+ "Type": "AWS::SageMaker::Model",
+ "Properties": {
+ "ExecutionRoleArn": {
+ "Fn::GetAtt": [
+ "SagemakerRole5FDB64E1",
+ "Arn"
+ ]
+ },
+ "PrimaryContainer": {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz"
+ },
+ "VpcConfig": {
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24",
+ "GroupId"
+ ]
+ }
+ ],
+ "Subnets": [
+ {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": {
+ "Type": "AWS::KMS::Key",
+ "Properties": {
+ "KeyPolicy": {
+ "Statement": [
+ {
+ "Action": [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource"
+ ],
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":root"
+ ]
+ ]
+ }
+ },
+ "Resource": "*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "EnableKeyRotation": true
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain"
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": {
+ "Type": "AWS::SageMaker::EndpointConfig",
+ "Properties": {
+ "ProductionVariants": [
+ {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName"
+ ]
+ },
+ "VariantName": "AllTraffic"
+ }
+ ],
+ "KmsKeyId": {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerModelEC3E4E39"
+ ]
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": {
+ "Type": "AWS::SageMaker::Endpoint",
+ "Properties": {
+ "EndpointConfigName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName"
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334"
+ ]
+ },
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Policies": [
+ {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/lambda/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy"
+ }
+ ]
+ }
+ },
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "ec2:CreateNetworkInterface",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:DeleteNetworkInterface",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC."
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/test-lambda-sagemaker/ReplaceDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerLambdaFunction661E043F": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Code": {
+ "S3Bucket": {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499"
+ },
+ "S3Key": {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::Select": [
+ 0,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Fn::Select": [
+ 1,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "Role": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ "Arn"
+ ]
+ },
+ "Environment": {
+ "Variables": {
+ "SAGEMAKER_ENDPOINT_NAME": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName"
+ ]
+ }
+ }
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": {
+ "Mode": "Active"
+ },
+ "VpcConfig": {
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810",
+ "GroupId"
+ ]
+ }
+ ],
+ "SubnetIds": [
+ {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ ],
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions."
+ }
+ ]
+ }
+ }
+ },
+ "Vpc8378EB38": {
+ "Type": "AWS::EC2::VPC",
+ "Properties": {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet1SubnetE62B1B9B": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.0.0/18",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1a",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet1RouteTableE442650B": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet1RouteTableAssociationD259E31A": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B"
+ },
+ "SubnetId": {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ }
+ }
+ },
+ "VpcisolatedSubnet2Subnet39217055": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.64.0/18",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1b",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet2RouteTable334F9764": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet2RouteTableAssociation25A4716F": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764"
+ },
+ "SubnetId": {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ }
+ }
+ },
+ "VpcisolatedSubnet3Subnet44F2537D": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.128.0/18",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1c",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet3RouteTableA2F6BBC0": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet3RouteTableAssociationDC010BEB": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcisolatedSubnet3RouteTableA2F6BBC0"
+ },
+ "SubnetId": {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ }
+ },
+ "VpcFlowLogIAMRole6A475D41": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "vpc-flow-logs.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": [
+ {
+ "Ref": "VpcFlowLogIAMRole6A475D41"
+ }
+ ]
+ }
+ },
+ "VpcFlowLogLogGroup7B5C56B9": {
+ "Type": "AWS::Logs::LogGroup",
+ "Properties": {
+ "RetentionInDays": 731
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain",
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)"
+ }
+ ]
+ }
+ }
+ },
+ "VpcFlowLog8FF33A73": {
+ "Type": "AWS::EC2::FlowLog",
+ "Properties": {
+ "ResourceId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "ResourceType": "VPC",
+ "TrafficType": "ALL",
+ "DeliverLogsPermissionArn": {
+ "Fn::GetAtt": [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn"
+ ]
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcS3A5408339": {
+ "Type": "AWS::EC2::VPCEndpoint",
+ "Properties": {
+ "ServiceName": {
+ "Fn::Join": [
+ "",
+ [
+ "com.amazonaws.",
+ {
+ "Ref": "AWS::Region"
+ },
+ ".s3"
+ ]
+ ]
+ },
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "RouteTableIds": [
+ {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3RouteTableA2F6BBC0"
+ }
+ ],
+ "VpcEndpointType": "Gateway"
+ }
+ },
+ "VpcSAGEMAKERRUNTIME337E125A": {
+ "Type": "AWS::EC2::VPCEndpoint",
+ "Properties": {
+ "ServiceName": {
+ "Fn::Join": [
+ "",
+ [
+ "com.amazonaws.",
+ {
+ "Ref": "AWS::Region"
+ },
+ ".sagemaker.runtime"
+ ]
+ ]
+ },
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "PrivateDnsEnabled": true,
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF",
+ "GroupId"
+ ]
+ }
+ ],
+ "SubnetIds": [
+ {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ ],
+ "VpcEndpointType": "Interface"
+ }
+ },
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/ReplaceEndpointDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "SecurityGroupIngress": [
+ {
+ "CidrIp": {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ "Description": {
+ "Fn::Join": [
+ "",
+ [
+ "from ",
+ {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ ":443"
+ ]
+ ]
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "Parameters": {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": {
+ "Type": "String",
+ "Description": "S3 bucket for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": {
+ "Type": "String",
+ "Description": "S3 key for asset version \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": {
+ "Type": "String",
+ "Description": "Artifact hash for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVPCandCustomRole.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVPCandCustomRole.ts
new file mode 100644
index 000000000..5b4d618e3
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVPCandCustomRole.ts
@@ -0,0 +1,52 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration, App } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as lambda from '@aws-cdk/aws-lambda';
+import * as iam from '@aws-cdk/aws-iam';
+
+// Setup
+const app = new App();
+const stack = new Stack(app, 'test-lambda-sagemakerendpoint');
+stack.templateOptions.description = 'Integration Test for aws-lambda-sagemakerendpoint';
+
+// Create IAM Role to be assumed by Sagemaker
+const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
+});
+sagemakerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSageMakerFullAccess'));
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ executionRoleArn: sagemakerRole.roleArn,
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ deployVpc: true,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+};
+
+new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+// Synth
+app.synth();
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVpc.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVpc.expected.json
new file mode 100644
index 000000000..a884b7c8f
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVpc.expected.json
@@ -0,0 +1,1102 @@
+{
+ "Description": "Integration Test for aws-lambda-sagemakerendpoint",
+ "Resources": {
+ "testlambdasagemakerSagemakerRoleD84546B8": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "sagemaker.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":sagemaker:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/sagemaker/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "ec2:CreateNetworkInterface",
+ "ec2:CreateNetworkInterfacePermission",
+ "ec2:DeleteNetworkInterface",
+ "ec2:DeleteNetworkInterfacePermission",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses",
+ "ec2:DescribeVpcs",
+ "ec2:DescribeDhcpOptions",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeSecurityGroups"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":ecr:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":repository/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":key/*"
+ ]
+ ]
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":alias/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*"
+ },
+ {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringLike": {
+ "iam:PassedToService": "sagemaker.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerSagemakerRoleD84546B8"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference."
+ },
+ {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/test-lambda-sagemaker/ReplaceModelDefaultSecurityGroup",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "SecurityGroupIngress": [
+ {
+ "CidrIp": {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ "Description": {
+ "Fn::Join": [
+ "",
+ [
+ "from ",
+ {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ ":443"
+ ]
+ ]
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": {
+ "Type": "AWS::SageMaker::Model",
+ "Properties": {
+ "ExecutionRoleArn": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ },
+ "PrimaryContainer": {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz"
+ },
+ "VpcConfig": {
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24",
+ "GroupId"
+ ]
+ }
+ ],
+ "Subnets": [
+ {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "testlambdasagemakerSagemakerRoleD84546B8"
+ ]
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": {
+ "Type": "AWS::KMS::Key",
+ "Properties": {
+ "KeyPolicy": {
+ "Statement": [
+ {
+ "Action": [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource"
+ ],
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":root"
+ ]
+ ]
+ }
+ },
+ "Resource": "*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "EnableKeyRotation": true
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain"
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": {
+ "Type": "AWS::SageMaker::EndpointConfig",
+ "Properties": {
+ "ProductionVariants": [
+ {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName"
+ ]
+ },
+ "VariantName": "AllTraffic"
+ }
+ ],
+ "KmsKeyId": {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerModelEC3E4E39"
+ ]
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": {
+ "Type": "AWS::SageMaker::Endpoint",
+ "Properties": {
+ "EndpointConfigName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName"
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334"
+ ]
+ },
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Policies": [
+ {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/lambda/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy"
+ }
+ ]
+ }
+ },
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "ec2:CreateNetworkInterface",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:DeleteNetworkInterface",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC."
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/test-lambda-sagemaker/ReplaceDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerLambdaFunction661E043F": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Code": {
+ "S3Bucket": {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499"
+ },
+ "S3Key": {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::Select": [
+ 0,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Fn::Select": [
+ 1,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "Role": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ "Arn"
+ ]
+ },
+ "Environment": {
+ "Variables": {
+ "SAGEMAKER_ENDPOINT_NAME": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName"
+ ]
+ }
+ }
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": {
+ "Mode": "Active"
+ },
+ "VpcConfig": {
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810",
+ "GroupId"
+ ]
+ }
+ ],
+ "SubnetIds": [
+ {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ ],
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions."
+ }
+ ]
+ }
+ }
+ },
+ "Vpc8378EB38": {
+ "Type": "AWS::EC2::VPC",
+ "Properties": {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet1SubnetE62B1B9B": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.0.0/18",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1a",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet1RouteTableE442650B": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet1RouteTableAssociationD259E31A": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B"
+ },
+ "SubnetId": {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ }
+ }
+ },
+ "VpcisolatedSubnet2Subnet39217055": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.64.0/18",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1b",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet2RouteTable334F9764": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet2RouteTableAssociation25A4716F": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764"
+ },
+ "SubnetId": {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ }
+ }
+ },
+ "VpcisolatedSubnet3Subnet44F2537D": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.128.0/18",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1c",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet3RouteTableA2F6BBC0": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/isolatedSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcisolatedSubnet3RouteTableAssociationDC010BEB": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcisolatedSubnet3RouteTableA2F6BBC0"
+ },
+ "SubnetId": {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ }
+ },
+ "VpcFlowLogIAMRole6A475D41": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "vpc-flow-logs.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": [
+ {
+ "Ref": "VpcFlowLogIAMRole6A475D41"
+ }
+ ]
+ }
+ },
+ "VpcFlowLogLogGroup7B5C56B9": {
+ "Type": "AWS::Logs::LogGroup",
+ "Properties": {
+ "RetentionInDays": 731
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain",
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)"
+ }
+ ]
+ }
+ }
+ },
+ "VpcFlowLog8FF33A73": {
+ "Type": "AWS::EC2::FlowLog",
+ "Properties": {
+ "ResourceId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "ResourceType": "VPC",
+ "TrafficType": "ALL",
+ "DeliverLogsPermissionArn": {
+ "Fn::GetAtt": [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn"
+ ]
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcS3A5408339": {
+ "Type": "AWS::EC2::VPCEndpoint",
+ "Properties": {
+ "ServiceName": {
+ "Fn::Join": [
+ "",
+ [
+ "com.amazonaws.",
+ {
+ "Ref": "AWS::Region"
+ },
+ ".s3"
+ ]
+ ]
+ },
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "RouteTableIds": [
+ {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3RouteTableA2F6BBC0"
+ }
+ ],
+ "VpcEndpointType": "Gateway"
+ }
+ },
+ "VpcSAGEMAKERRUNTIME337E125A": {
+ "Type": "AWS::EC2::VPCEndpoint",
+ "Properties": {
+ "ServiceName": {
+ "Fn::Join": [
+ "",
+ [
+ "com.amazonaws.",
+ {
+ "Ref": "AWS::Region"
+ },
+ ".sagemaker.runtime"
+ ]
+ ]
+ },
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "PrivateDnsEnabled": true,
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF",
+ "GroupId"
+ ]
+ }
+ ],
+ "SubnetIds": [
+ {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B"
+ },
+ {
+ "Ref": "VpcisolatedSubnet2Subnet39217055"
+ },
+ {
+ "Ref": "VpcisolatedSubnet3Subnet44F2537D"
+ }
+ ],
+ "VpcEndpointType": "Interface"
+ }
+ },
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/ReplaceEndpointDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "SecurityGroupIngress": [
+ {
+ "CidrIp": {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ "Description": {
+ "Fn::Join": [
+ "",
+ [
+ "from ",
+ {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ ":443"
+ ]
+ ]
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "Parameters": {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": {
+ "Type": "String",
+ "Description": "S3 bucket for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": {
+ "Type": "String",
+ "Description": "S3 key for asset version \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": {
+ "Type": "String",
+ "Description": "Artifact hash for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVpc.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVpc.ts
new file mode 100644
index 000000000..1d913437e
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithVpc.ts
@@ -0,0 +1,44 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration, App } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as lambda from '@aws-cdk/aws-lambda';
+
+// Setup
+const app = new App();
+const stack = new Stack(app, 'test-lambda-sagemakerendpoint');
+stack.templateOptions.description = 'Integration Test for aws-lambda-sagemakerendpoint';
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ deployVpc: true,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+};
+
+new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+// Synth
+app.synth();
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithexistingVpc.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithexistingVpc.expected.json
new file mode 100644
index 000000000..fc5fa3d1b
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithexistingVpc.expected.json
@@ -0,0 +1,1490 @@
+{
+ "Description": "Integration Test for aws-lambda-sagemakerendpoint",
+ "Resources": {
+ "Vpc8378EB38": {
+ "Type": "AWS::EC2::VPC",
+ "Properties": {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet1Subnet5C2D37C4": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.0.0/19",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1a",
+ "MapPublicIpOnLaunch": true,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet1"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true"
+ }
+ ]
+ }
+ }
+ },
+ "VpcPublicSubnet1RouteTable6C95E38E": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet1RouteTableAssociation97140677": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E"
+ },
+ "SubnetId": {
+ "Ref": "VpcPublicSubnet1Subnet5C2D37C4"
+ }
+ }
+ },
+ "VpcPublicSubnet1DefaultRoute3DA9E72A": {
+ "Type": "AWS::EC2::Route",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E"
+ },
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": {
+ "Ref": "VpcIGWD7BA715C"
+ }
+ },
+ "DependsOn": [
+ "VpcVPCGWBF912B6E"
+ ]
+ },
+ "VpcPublicSubnet1EIPD7E02669": {
+ "Type": "AWS::EC2::EIP",
+ "Properties": {
+ "Domain": "vpc",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet1NATGateway4D7517AA": {
+ "Type": "AWS::EC2::NatGateway",
+ "Properties": {
+ "AllocationId": {
+ "Fn::GetAtt": [
+ "VpcPublicSubnet1EIPD7E02669",
+ "AllocationId"
+ ]
+ },
+ "SubnetId": {
+ "Ref": "VpcPublicSubnet1Subnet5C2D37C4"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet2Subnet691E08A3": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.32.0/19",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1b",
+ "MapPublicIpOnLaunch": true,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet2"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true"
+ }
+ ]
+ }
+ }
+ },
+ "VpcPublicSubnet2RouteTable94F7E489": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet2RouteTableAssociationDD5762D8": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489"
+ },
+ "SubnetId": {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3"
+ }
+ }
+ },
+ "VpcPublicSubnet2DefaultRoute97F91067": {
+ "Type": "AWS::EC2::Route",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489"
+ },
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": {
+ "Ref": "VpcIGWD7BA715C"
+ }
+ },
+ "DependsOn": [
+ "VpcVPCGWBF912B6E"
+ ]
+ },
+ "VpcPublicSubnet2EIP3C605A87": {
+ "Type": "AWS::EC2::EIP",
+ "Properties": {
+ "Domain": "vpc",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet2NATGateway9182C01D": {
+ "Type": "AWS::EC2::NatGateway",
+ "Properties": {
+ "AllocationId": {
+ "Fn::GetAtt": [
+ "VpcPublicSubnet2EIP3C605A87",
+ "AllocationId"
+ ]
+ },
+ "SubnetId": {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet3SubnetBE12F0B6": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.64.0/19",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1c",
+ "MapPublicIpOnLaunch": true,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet3"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true"
+ }
+ ]
+ }
+ }
+ },
+ "VpcPublicSubnet3RouteTable93458DBB": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet3RouteTableAssociation1F1EDF02": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPublicSubnet3RouteTable93458DBB"
+ },
+ "SubnetId": {
+ "Ref": "VpcPublicSubnet3SubnetBE12F0B6"
+ }
+ }
+ },
+ "VpcPublicSubnet3DefaultRoute4697774F": {
+ "Type": "AWS::EC2::Route",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPublicSubnet3RouteTable93458DBB"
+ },
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": {
+ "Ref": "VpcIGWD7BA715C"
+ }
+ },
+ "DependsOn": [
+ "VpcVPCGWBF912B6E"
+ ]
+ },
+ "VpcPublicSubnet3EIP3A666A23": {
+ "Type": "AWS::EC2::EIP",
+ "Properties": {
+ "Domain": "vpc",
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcPublicSubnet3NATGateway7640CD1D": {
+ "Type": "AWS::EC2::NatGateway",
+ "Properties": {
+ "AllocationId": {
+ "Fn::GetAtt": [
+ "VpcPublicSubnet3EIP3A666A23",
+ "AllocationId"
+ ]
+ },
+ "SubnetId": {
+ "Ref": "VpcPublicSubnet3SubnetBE12F0B6"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PublicSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet1Subnet536B997A": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.96.0/19",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1a",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PrivateSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet1RouteTableB2C5B500": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PrivateSubnet1"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet1RouteTableAssociation70C59FA6": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
+ },
+ "SubnetId": {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A"
+ }
+ }
+ },
+ "VpcPrivateSubnet1DefaultRouteBE02A9ED": {
+ "Type": "AWS::EC2::Route",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
+ },
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": {
+ "Ref": "VpcPublicSubnet1NATGateway4D7517AA"
+ }
+ }
+ },
+ "VpcPrivateSubnet2Subnet3788AAA1": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.128.0/19",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1b",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PrivateSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet2RouteTableA678073B": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PrivateSubnet2"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet2RouteTableAssociationA89CAD56": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B"
+ },
+ "SubnetId": {
+ "Ref": "VpcPrivateSubnet2Subnet3788AAA1"
+ }
+ }
+ },
+ "VpcPrivateSubnet2DefaultRoute060D2087": {
+ "Type": "AWS::EC2::Route",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B"
+ },
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": {
+ "Ref": "VpcPublicSubnet2NATGateway9182C01D"
+ }
+ }
+ },
+ "VpcPrivateSubnet3SubnetF258B56E": {
+ "Type": "AWS::EC2::Subnet",
+ "Properties": {
+ "CidrBlock": "10.0.160.0/19",
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "AvailabilityZone": "test-region-1c",
+ "MapPublicIpOnLaunch": false,
+ "Tags": [
+ {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private"
+ },
+ {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private"
+ },
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PrivateSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet3RouteTableD98824C7": {
+ "Type": "AWS::EC2::RouteTable",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc/PrivateSubnet3"
+ }
+ ]
+ }
+ },
+ "VpcPrivateSubnet3RouteTableAssociation16BDDC43": {
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPrivateSubnet3RouteTableD98824C7"
+ },
+ "SubnetId": {
+ "Ref": "VpcPrivateSubnet3SubnetF258B56E"
+ }
+ }
+ },
+ "VpcPrivateSubnet3DefaultRoute94B74F0D": {
+ "Type": "AWS::EC2::Route",
+ "Properties": {
+ "RouteTableId": {
+ "Ref": "VpcPrivateSubnet3RouteTableD98824C7"
+ },
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": {
+ "Ref": "VpcPublicSubnet3NATGateway7640CD1D"
+ }
+ }
+ },
+ "VpcIGWD7BA715C": {
+ "Type": "AWS::EC2::InternetGateway",
+ "Properties": {
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcVPCGWBF912B6E": {
+ "Type": "AWS::EC2::VPCGatewayAttachment",
+ "Properties": {
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "InternetGatewayId": {
+ "Ref": "VpcIGWD7BA715C"
+ }
+ }
+ },
+ "VpcFlowLogIAMRole6A475D41": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "vpc-flow-logs.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": [
+ {
+ "Ref": "VpcFlowLogIAMRole6A475D41"
+ }
+ ]
+ }
+ },
+ "VpcFlowLogLogGroup7B5C56B9": {
+ "Type": "AWS::Logs::LogGroup",
+ "Properties": {
+ "RetentionInDays": 731
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain",
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)"
+ }
+ ]
+ }
+ }
+ },
+ "VpcFlowLog8FF33A73": {
+ "Type": "AWS::EC2::FlowLog",
+ "Properties": {
+ "ResourceId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "ResourceType": "VPC",
+ "TrafficType": "ALL",
+ "DeliverLogsPermissionArn": {
+ "Fn::GetAtt": [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn"
+ ]
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9"
+ },
+ "Tags": [
+ {
+ "Key": "Name",
+ "Value": "test-lambda-sagemakerendpoint/Vpc"
+ }
+ ]
+ }
+ },
+ "VpcS3A5408339": {
+ "Type": "AWS::EC2::VPCEndpoint",
+ "Properties": {
+ "ServiceName": {
+ "Fn::Join": [
+ "",
+ [
+ "com.amazonaws.",
+ {
+ "Ref": "AWS::Region"
+ },
+ ".s3"
+ ]
+ ]
+ },
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "RouteTableIds": [
+ {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
+ },
+ {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B"
+ },
+ {
+ "Ref": "VpcPrivateSubnet3RouteTableD98824C7"
+ },
+ {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E"
+ },
+ {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489"
+ },
+ {
+ "Ref": "VpcPublicSubnet3RouteTable93458DBB"
+ }
+ ],
+ "VpcEndpointType": "Gateway"
+ }
+ },
+ "VpcSAGEMAKERRUNTIME337E125A": {
+ "Type": "AWS::EC2::VPCEndpoint",
+ "Properties": {
+ "ServiceName": {
+ "Fn::Join": [
+ "",
+ [
+ "com.amazonaws.",
+ {
+ "Ref": "AWS::Region"
+ },
+ ".sagemaker.runtime"
+ ]
+ ]
+ },
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ },
+ "PrivateDnsEnabled": true,
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF",
+ "GroupId"
+ ]
+ }
+ ],
+ "SubnetIds": [
+ {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A"
+ },
+ {
+ "Ref": "VpcPrivateSubnet2Subnet3788AAA1"
+ },
+ {
+ "Ref": "VpcPrivateSubnet3SubnetF258B56E"
+ }
+ ],
+ "VpcEndpointType": "Interface"
+ }
+ },
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/ReplaceEndpointDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "SecurityGroupIngress": [
+ {
+ "CidrIp": {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ "Description": {
+ "Fn::Join": [
+ "",
+ [
+ "from ",
+ {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ ":443"
+ ]
+ ]
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerRoleD84546B8": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "sagemaker.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":sagemaker:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/sagemaker/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "ec2:CreateNetworkInterface",
+ "ec2:CreateNetworkInterfacePermission",
+ "ec2:DeleteNetworkInterface",
+ "ec2:DeleteNetworkInterfacePermission",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses",
+ "ec2:DescribeVpcs",
+ "ec2:DescribeDhcpOptions",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeSecurityGroups"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":ecr:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":repository/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":key/*"
+ ]
+ ]
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":alias/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*"
+ },
+ {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringLike": {
+ "iam:PassedToService": "sagemaker.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerSagemakerRoleD84546B8"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference."
+ },
+ {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/test-lambda-sagemaker/ReplaceModelDefaultSecurityGroup",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "SecurityGroupIngress": [
+ {
+ "CidrIp": {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ "Description": {
+ "Fn::Join": [
+ "",
+ [
+ "from ",
+ {
+ "Fn::GetAtt": [
+ "Vpc8378EB38",
+ "CidrBlock"
+ ]
+ },
+ ":443"
+ ]
+ ]
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": {
+ "Type": "AWS::SageMaker::Model",
+ "Properties": {
+ "ExecutionRoleArn": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ },
+ "PrimaryContainer": {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz"
+ },
+ "VpcConfig": {
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "testlambdasagemakerReplaceModelDefaultSecurityGroup7284AA24",
+ "GroupId"
+ ]
+ }
+ ],
+ "Subnets": [
+ {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A"
+ },
+ {
+ "Ref": "VpcPrivateSubnet2Subnet3788AAA1"
+ },
+ {
+ "Ref": "VpcPrivateSubnet3SubnetF258B56E"
+ }
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "testlambdasagemakerSagemakerRoleD84546B8"
+ ]
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": {
+ "Type": "AWS::KMS::Key",
+ "Properties": {
+ "KeyPolicy": {
+ "Statement": [
+ {
+ "Action": [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource"
+ ],
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":root"
+ ]
+ ]
+ }
+ },
+ "Resource": "*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "EnableKeyRotation": true
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain"
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": {
+ "Type": "AWS::SageMaker::EndpointConfig",
+ "Properties": {
+ "ProductionVariants": [
+ {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName"
+ ]
+ },
+ "VariantName": "AllTraffic"
+ }
+ ],
+ "KmsKeyId": {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerModelEC3E4E39"
+ ]
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": {
+ "Type": "AWS::SageMaker::Endpoint",
+ "Properties": {
+ "EndpointConfigName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName"
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334"
+ ]
+ },
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Policies": [
+ {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/lambda/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy"
+ }
+ ]
+ }
+ },
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "ec2:CreateNetworkInterface",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:DeleteNetworkInterface",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC."
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810": {
+ "Type": "AWS::EC2::SecurityGroup",
+ "Properties": {
+ "GroupDescription": "test-lambda-sagemakerendpoint/test-lambda-sagemaker/ReplaceDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": [
+ {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1"
+ }
+ ],
+ "VpcId": {
+ "Ref": "Vpc8378EB38"
+ }
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK"
+ },
+ {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerLambdaFunction661E043F": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Code": {
+ "S3Bucket": {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499"
+ },
+ "S3Key": {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::Select": [
+ 0,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Fn::Select": [
+ 1,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "Role": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ "Arn"
+ ]
+ },
+ "Environment": {
+ "Variables": {
+ "SAGEMAKER_ENDPOINT_NAME": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName"
+ ]
+ }
+ }
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": {
+ "Mode": "Active"
+ },
+ "VpcConfig": {
+ "SecurityGroupIds": [
+ {
+ "Fn::GetAtt": [
+ "testlambdasagemakerReplaceDefaultSecurityGroupsecuritygroupB2FD7810",
+ "GroupId"
+ ]
+ }
+ ],
+ "SubnetIds": [
+ {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A"
+ },
+ {
+ "Ref": "VpcPrivateSubnet2Subnet3788AAA1"
+ },
+ {
+ "Ref": "VpcPrivateSubnet3SubnetF258B56E"
+ }
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ ],
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions."
+ }
+ ]
+ }
+ }
+ }
+ },
+ "Parameters": {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": {
+ "Type": "String",
+ "Description": "S3 bucket for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": {
+ "Type": "String",
+ "Description": "S3 key for asset version \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": {
+ "Type": "String",
+ "Description": "Artifact hash for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithexistingVpc.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithexistingVpc.ts
new file mode 100644
index 000000000..1eafb9f7e
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.deployFunctionWithexistingVpc.ts
@@ -0,0 +1,58 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration, App } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as defaults from '@aws-solutions-constructs/core';
+import * as lambda from '@aws-cdk/aws-lambda';
+
+// Setup
+const app = new App();
+const stack = new Stack(app, 'test-lambda-sagemakerendpoint');
+stack.templateOptions.description = 'Integration Test for aws-lambda-sagemakerendpoint';
+
+const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultPublicPrivateVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+});
+
+// Add S3 VPC Gateway Endpint, required by Sagemaker to access Models artifacts via AWS private network
+defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.S3);
+// Add SAGEMAKER_RUNTIME VPC Interface Endpint, required by the lambda function to invoke the SageMaker endpoint
+defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ existingVpc: vpc,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+};
+
+new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+// Synth
+app.synth();
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingFunction.expected.json
new file mode 100644
index 000000000..38ca3190a
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingFunction.expected.json
@@ -0,0 +1,541 @@
+{
+ "Description": "Integration Test for aws-lambda-sagemakerendpoint",
+ "Resources": {
+ "LambdaFunctionServiceRole0C4CDE0B": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Policies": [
+ {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/lambda/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy"
+ }
+ ]
+ }
+ },
+ "LambdaFunctionServiceRoleDefaultPolicy126C8897": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": {
+ "Ref": "testlambdasagemakerSagemakerEndpoint12803730"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897",
+ "Roles": [
+ {
+ "Ref": "LambdaFunctionServiceRole0C4CDE0B"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC."
+ }
+ ]
+ }
+ }
+ },
+ "LambdaFunctionBF21E41F": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Code": {
+ "S3Bucket": {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499"
+ },
+ "S3Key": {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::Select": [
+ 0,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Fn::Select": [
+ 1,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "Role": {
+ "Fn::GetAtt": [
+ "LambdaFunctionServiceRole0C4CDE0B",
+ "Arn"
+ ]
+ },
+ "Environment": {
+ "Variables": {
+ "SAGEMAKER_ENDPOINT_NAME": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpoint12803730",
+ "EndpointName"
+ ]
+ }
+ }
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": {
+ "Mode": "Active"
+ }
+ },
+ "DependsOn": [
+ "LambdaFunctionServiceRoleDefaultPolicy126C8897",
+ "LambdaFunctionServiceRole0C4CDE0B"
+ ],
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions."
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerRoleD84546B8": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "sagemaker.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":sagemaker:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/sagemaker/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":ecr:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":repository/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":key/*"
+ ]
+ ]
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":alias/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*"
+ },
+ {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringLike": {
+ "iam:PassedToService": "sagemaker.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerSagemakerRoleD84546B8"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference."
+ },
+ {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services"
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerSagemakerModelEC3E4E39": {
+ "Type": "AWS::SageMaker::Model",
+ "Properties": {
+ "ExecutionRoleArn": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerRoleD84546B8",
+ "Arn"
+ ]
+ },
+ "PrimaryContainer": {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerRoleDefaultPolicy9909C0A0",
+ "testlambdasagemakerSagemakerRoleD84546B8"
+ ]
+ },
+ "testlambdasagemakerEncryptionKey2AACF9E0": {
+ "Type": "AWS::KMS::Key",
+ "Properties": {
+ "KeyPolicy": {
+ "Statement": [
+ {
+ "Action": [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource"
+ ],
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":root"
+ ]
+ ]
+ }
+ },
+ "Resource": "*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "EnableKeyRotation": true
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain"
+ },
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334": {
+ "Type": "AWS::SageMaker::EndpointConfig",
+ "Properties": {
+ "ProductionVariants": [
+ {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerModelEC3E4E39",
+ "ModelName"
+ ]
+ },
+ "VariantName": "AllTraffic"
+ }
+ ],
+ "KmsKeyId": {
+ "Ref": "testlambdasagemakerEncryptionKey2AACF9E0"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerModelEC3E4E39"
+ ]
+ },
+ "testlambdasagemakerSagemakerEndpoint12803730": {
+ "Type": "AWS::SageMaker::Endpoint",
+ "Properties": {
+ "EndpointConfigName": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334",
+ "EndpointConfigName"
+ ]
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerSagemakerEndpointConfig6BABA334"
+ ]
+ }
+ },
+ "Parameters": {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": {
+ "Type": "String",
+ "Description": "S3 bucket for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": {
+ "Type": "String",
+ "Description": "S3 key for asset version \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": {
+ "Type": "String",
+ "Description": "Artifact hash for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingFunction.ts
new file mode 100644
index 000000000..f3c4bbccf
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingFunction.ts
@@ -0,0 +1,47 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration, App } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as defaults from '@aws-solutions-constructs/core';
+import * as lambda from '@aws-cdk/aws-lambda';
+
+// Setup
+const app = new App();
+const stack = new Stack(app, 'test-lambda-sagemakerendpoint');
+stack.templateOptions.description = 'Integration Test for aws-lambda-sagemakerendpoint';
+
+// deploy lambda function
+const fn = defaults.deployLambdaFunction(stack, {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+});
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ existingLambdaObj: fn,
+};
+
+new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+// Synth
+app.synth();
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingSageMakerEndpoint.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingSageMakerEndpoint.expected.json
new file mode 100644
index 000000000..64cb30c15
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingSageMakerEndpoint.expected.json
@@ -0,0 +1,541 @@
+{
+ "Description": "Integration Test for aws-lambda-sagemakerendpoint",
+ "Resources": {
+ "SagemakerRole5FDB64E1": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "sagemaker.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ },
+ "SagemakerRoleDefaultPolicy9DD21C3C": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":sagemaker:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/sagemaker/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":ecr:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":repository/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":key/*"
+ ]
+ ]
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":kms:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":alias/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*"
+ },
+ {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "SagemakerRole5FDB64E1",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringLike": {
+ "iam:PassedToService": "sagemaker.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "SagemakerRole5FDB64E1",
+ "Arn"
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": [
+ {
+ "Ref": "SagemakerRole5FDB64E1"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference."
+ },
+ {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services"
+ }
+ ]
+ }
+ }
+ },
+ "SagemakerModel": {
+ "Type": "AWS::SageMaker::Model",
+ "Properties": {
+ "ExecutionRoleArn": {
+ "Fn::GetAtt": [
+ "SagemakerRole5FDB64E1",
+ "Arn"
+ ]
+ },
+ "PrimaryContainer": {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz"
+ }
+ },
+ "DependsOn": [
+ "SagemakerRoleDefaultPolicy9DD21C3C",
+ "SagemakerRole5FDB64E1"
+ ]
+ },
+ "EncryptionKey1B843E66": {
+ "Type": "AWS::KMS::Key",
+ "Properties": {
+ "KeyPolicy": {
+ "Statement": [
+ {
+ "Action": [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource"
+ ],
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":iam::",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":root"
+ ]
+ ]
+ }
+ },
+ "Resource": "*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "EnableKeyRotation": true
+ },
+ "UpdateReplacePolicy": "Retain",
+ "DeletionPolicy": "Retain"
+ },
+ "SagemakerEndpointConfig": {
+ "Type": "AWS::SageMaker::EndpointConfig",
+ "Properties": {
+ "ProductionVariants": [
+ {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": {
+ "Fn::GetAtt": [
+ "SagemakerModel",
+ "ModelName"
+ ]
+ },
+ "VariantName": "AllTraffic"
+ }
+ ],
+ "KmsKeyId": {
+ "Ref": "EncryptionKey1B843E66"
+ }
+ },
+ "DependsOn": [
+ "SagemakerModel"
+ ]
+ },
+ "SagemakerEndpoint": {
+ "Type": "AWS::SageMaker::Endpoint",
+ "Properties": {
+ "EndpointConfigName": {
+ "Fn::GetAtt": [
+ "SagemakerEndpointConfig",
+ "EndpointConfigName"
+ ]
+ }
+ },
+ "DependsOn": [
+ "SagemakerEndpointConfig"
+ ]
+ },
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "Policies": [
+ {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":logs:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":log-group:/aws/lambda/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "LambdaFunctionServiceRolePolicy"
+ }
+ ]
+ }
+ },
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "xray:PutTraceSegments",
+ "xray:PutTelemetryRecords"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "sagemaker:InvokeEndpoint",
+ "Effect": "Allow",
+ "Resource": {
+ "Ref": "SagemakerEndpoint"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "Roles": [
+ {
+ "Ref": "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ }
+ ]
+ },
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W12",
+ "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC."
+ }
+ ]
+ }
+ }
+ },
+ "testlambdasagemakerLambdaFunction661E043F": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Code": {
+ "S3Bucket": {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499"
+ },
+ "S3Key": {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::Select": [
+ 0,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Fn::Select": [
+ 1,
+ {
+ "Fn::Split": [
+ "||",
+ {
+ "Ref": "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "Role": {
+ "Fn::GetAtt": [
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB",
+ "Arn"
+ ]
+ },
+ "Environment": {
+ "Variables": {
+ "SAGEMAKER_ENDPOINT_NAME": {
+ "Fn::GetAtt": [
+ "SagemakerEndpoint",
+ "EndpointName"
+ ]
+ }
+ }
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Runtime": "python3.8",
+ "Timeout": 300,
+ "TracingConfig": {
+ "Mode": "Active"
+ }
+ },
+ "DependsOn": [
+ "testlambdasagemakerLambdaFunctionServiceRoleDefaultPolicy208C2512",
+ "testlambdasagemakerLambdaFunctionServiceRole4BA038CB"
+ ],
+ "Metadata": {
+ "cfn_nag": {
+ "rules_to_suppress": [
+ {
+ "id": "W58",
+ "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions."
+ }
+ ]
+ }
+ }
+ }
+ },
+ "Parameters": {
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3BucketE0481499": {
+ "Type": "String",
+ "Description": "S3 bucket for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15S3VersionKey9A1AB349": {
+ "Type": "String",
+ "Description": "S3 key for asset version \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ },
+ "AssetParametersd894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15ArtifactHash4C89D4A0": {
+ "Type": "String",
+ "Description": "Artifact hash for asset \"d894a15aa0242919d44274cbb8ddd33f39cce242789e85e67e642da0a2926e15\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingSageMakerEndpoint.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingSageMakerEndpoint.ts
new file mode 100644
index 000000000..2bc5bdedb
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/integ.existingSageMakerEndpoint.ts
@@ -0,0 +1,48 @@
+/**
+ * Copyright 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. 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.
+ */
+
+// Imports
+import { Stack, Duration, App } from '@aws-cdk/core';
+import { LambdaToSagemakerEndpoint, LambdaToSagemakerEndpointProps } from '../lib';
+import * as defaults from '@aws-solutions-constructs/core';
+import * as lambda from '@aws-cdk/aws-lambda';
+
+// Setup
+const app = new App();
+const stack = new Stack(app, 'test-lambda-sagemakerendpoint');
+stack.templateOptions.description = 'Integration Test for aws-lambda-sagemakerendpoint';
+
+const [sagemakerEndpoint] = defaults.deploySagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+});
+
+const constructProps: LambdaToSagemakerEndpointProps = {
+ existingSagemakerEndpointObj: sagemakerEndpoint,
+ lambdaFunctionProps: {
+ runtime: lambda.Runtime.PYTHON_3_8,
+ code: lambda.Code.fromAsset(`${__dirname}/lambda`),
+ handler: 'index.handler',
+ timeout: Duration.minutes(5),
+ memorySize: 128,
+ },
+};
+
+new LambdaToSagemakerEndpoint(stack, 'test-lambda-sagemaker', constructProps);
+
+// Synth
+app.synth();
diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/lambda/index.py b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/lambda/index.py
new file mode 100644
index 000000000..c4d6d0e38
--- /dev/null
+++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sagemakerendpoint/test/lambda/index.py
@@ -0,0 +1,26 @@
+import os
+import json
+import boto3
+
+sagemaker_client = boto3.client("sagemaker-runtime")
+
+def handler(event, context):
+ event_body = json.loads(event["body"])
+ endpoint_name = os.environ["SAGEMAKER_ENDPOINT_NAME"]
+ return invoke(event_body, endpoint_name)
+
+def invoke(event_body, endpoint_name, sm_client=sagemaker_client):
+ response = sm_client.invoke_endpoint(
+ EndpointName=endpoint_name,
+ Body=event_body["payload"],
+ ContentType=event_body["ContentType"]
+ )
+ print(response)
+ predictions = response['Body'].read().decode()
+ print(predictions)
+ return {
+ "statusCode": 200,
+ "isBase64Encoded": False,
+ "body": predictions,
+ "headers": {"Content-Type": "plain/text"},
+ }
diff --git a/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-defaults.ts
index ee13f417c..6e5129b77 100644
--- a/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-defaults.ts
+++ b/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-defaults.ts
@@ -11,15 +11,58 @@
* and limitations under the License.
*/
-import { CfnNotebookInstanceProps } from '@aws-cdk/aws-sagemaker';
+import {
+ CfnNotebookInstanceProps,
+ CfnModelProps,
+ CfnModel,
+ CfnEndpointConfigProps,
+ CfnEndpointProps,
+} from '@aws-cdk/aws-sagemaker';
-export function DefaultSagemakerNotebookProps(roleArn: string, kmsKeyId: string,
- subnetId?: string, securityGroupIds?: string[]): CfnNotebookInstanceProps {
+export function DefaultSagemakerNotebookProps(
+ roleArn: string,
+ kmsKeyId: string,
+ subnetId?: string,
+ securityGroupIds?: string[]
+): CfnNotebookInstanceProps {
return {
instanceType: 'ml.t2.medium',
roleArn,
kmsKeyId,
- ... subnetId && {subnetId, directInternetAccess: 'Disabled'},
- ... securityGroupIds && {securityGroupIds},
+ ...(subnetId && { subnetId, directInternetAccess: 'Disabled' }),
+ ...(securityGroupIds && { securityGroupIds }),
} as CfnNotebookInstanceProps;
-}
\ No newline at end of file
+}
+
+export function DefaultSagemakerModelProps(
+ executionRoleArn: string,
+ primaryContainer: CfnModel.ContainerDefinitionProperty,
+ vpcConfig?: CfnModel.VpcConfigProperty
+): CfnModelProps {
+ return {
+ executionRoleArn,
+ primaryContainer,
+ ...(vpcConfig && { vpcConfig }),
+ } as CfnModelProps;
+}
+
+export function DefaultSagemakerEndpointConfigProps(modelName: string, kmsKeyId?: string): CfnEndpointConfigProps {
+ return {
+ productionVariants: [
+ {
+ modelName,
+ initialInstanceCount: 1,
+ initialVariantWeight: 1.0,
+ instanceType: 'ml.m4.xlarge',
+ variantName: 'AllTraffic',
+ },
+ ],
+ ...(kmsKeyId && { kmsKeyId }),
+ } as CfnEndpointConfigProps;
+}
+
+export function DefaultSagemakerEndpointProps(endpointConfigName: string): CfnEndpointProps {
+ return {
+ endpointConfigName,
+ } as CfnEndpointProps;
+}
diff --git a/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-helper.ts
index d9dbe89d1..564751b71 100644
--- a/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-helper.ts
+++ b/source/patterns/@aws-solutions-constructs/core/lib/sagemaker-helper.ts
@@ -11,17 +11,22 @@
* and limitations under the License.
*/
-import * as sagemaker from "@aws-cdk/aws-sagemaker";
-import * as ec2 from "@aws-cdk/aws-ec2";
-import { buildEncryptionKey } from "./kms-helper";
-import { DefaultSagemakerNotebookProps } from "./sagemaker-defaults";
-import * as cdk from "@aws-cdk/core";
-import { overrideProps } from "./utils";
+import * as sagemaker from '@aws-cdk/aws-sagemaker';
+import * as ec2 from '@aws-cdk/aws-ec2';
+import { buildEncryptionKey } from './kms-helper';
+import {
+ DefaultSagemakerNotebookProps,
+ DefaultSagemakerModelProps,
+ DefaultSagemakerEndpointConfigProps,
+ DefaultSagemakerEndpointProps,
+} from './sagemaker-defaults';
+import * as cdk from '@aws-cdk/core';
+import { overrideProps } from './utils';
import { buildVpc } from './vpc-helper';
import * as iam from '@aws-cdk/aws-iam';
-import { Aws } from "@aws-cdk/core";
-import { DefaultPublicPrivateVpcProps } from "./vpc-defaults";
-import { buildSecurityGroup } from "./security-group-helper";
+import { Aws } from '@aws-cdk/core';
+import { DefaultPublicPrivateVpcProps } from './vpc-defaults';
+import { buildSecurityGroup } from './security-group-helper';
export interface BuildSagemakerNotebookProps {
/**
@@ -42,71 +47,179 @@ export interface BuildSagemakerNotebookProps {
*
* @default - None
*/
- readonly existingNotebookObj?: sagemaker.CfnNotebookInstance,
+ readonly existingNotebookObj?: sagemaker.CfnNotebookInstance;
/**
- * IAM Role Arn for SageMaker NoteBookInstance
+ * IAM Role Arn for Sagemaker NoteBookInstance
*
* @default - None
*/
- readonly role: iam.Role
+ readonly role: iam.Role;
}
-function addPermissions(_role: iam.Role) {
-
+function addPermissions(_role: iam.Role, props?: BuildSagemakerEndpointProps) {
// Grant permissions to NoteBookInstance for creating and training the model
- _role.addToPolicy(new iam.PolicyStatement({
- resources: [`arn:${Aws.PARTITION}:sagemaker:${Aws.REGION}:${Aws.ACCOUNT_ID}:*`],
- actions: [
- "sagemaker:CreateTrainingJob",
- "sagemaker:DescribeTrainingJob",
- "sagemaker:CreateModel",
- "sagemaker:DescribeModel",
- "sagemaker:DeleteModel",
- "sagemaker:CreateEndpoint",
- "sagemaker:CreateEndpointConfig",
- "sagemaker:DescribeEndpoint",
- "sagemaker:DescribeEndpointConfig",
- "sagemaker:DeleteEndpoint",
- "sagemaker:DeleteEndpointConfig",
- "sagemaker:InvokeEndpoint"
- ],
- }));
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: [`arn:${Aws.PARTITION}:sagemaker:${Aws.REGION}:${Aws.ACCOUNT_ID}:*`],
+ actions: [
+ 'sagemaker:CreateTrainingJob',
+ 'sagemaker:DescribeTrainingJob',
+ 'sagemaker:CreateModel',
+ 'sagemaker:DescribeModel',
+ 'sagemaker:DeleteModel',
+ 'sagemaker:CreateEndpoint',
+ 'sagemaker:CreateEndpointConfig',
+ 'sagemaker:DescribeEndpoint',
+ 'sagemaker:DescribeEndpointConfig',
+ 'sagemaker:DeleteEndpoint',
+ 'sagemaker:DeleteEndpointConfig',
+ 'sagemaker:InvokeEndpoint',
+ ],
+ })
+ );
// Grant CloudWatch Logging permissions
- _role.addToPolicy(new iam.PolicyStatement({
- resources: [`arn:${cdk.Aws.PARTITION}:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/sagemaker/*`],
- actions: [
- 'logs:CreateLogGroup',
- 'logs:CreateLogStream',
- 'logs:DescribeLogStreams',
- 'logs:GetLogEvents',
- 'logs:PutLogEvents'
- ],
- }));
-
- // Grant GetRole permissions to the SageMaker service
- _role.addToPolicy(new iam.PolicyStatement({
- resources: [_role.roleArn],
- actions: [
- 'iam:GetRole'
- ],
- }));
-
- // Grant PassRole permissions to the SageMaker service
- _role.addToPolicy(new iam.PolicyStatement({
- resources: [_role.roleArn],
- actions: [
- 'iam:PassRole'
- ],
- conditions: {
- StringLike: {'iam:PassedToService': 'sagemaker.amazonaws.com'}
- },
- }));
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: [`arn:${cdk.Aws.PARTITION}:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/sagemaker/*`],
+ actions: [
+ 'logs:CreateLogGroup',
+ 'logs:CreateLogStream',
+ 'logs:DescribeLogStreams',
+ 'logs:GetLogEvents',
+ 'logs:PutLogEvents',
+ ],
+ })
+ );
+
+ // To place the Sagemaker endpoint in a VPC
+ if (props && props.vpc) {
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: ['*'],
+ actions: [
+ 'ec2:CreateNetworkInterface',
+ 'ec2:CreateNetworkInterfacePermission',
+ 'ec2:DeleteNetworkInterface',
+ 'ec2:DeleteNetworkInterfacePermission',
+ 'ec2:DescribeNetworkInterfaces',
+ 'ec2:AssignPrivateIpAddresses',
+ 'ec2:UnassignPrivateIpAddresses',
+ 'ec2:DescribeVpcs',
+ 'ec2:DescribeDhcpOptions',
+ 'ec2:DescribeSubnets',
+ 'ec2:DescribeSecurityGroups',
+ ],
+ })
+ );
+ }
+
+ // To create a Sagemaker model using Bring-Your-Own-Model (BYOM) algorith image
+ // The image URL is specified in the modelProps
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: [`arn:${cdk.Aws.PARTITION}:ecr:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:repository/*`],
+ actions: [
+ 'ecr:BatchCheckLayerAvailability',
+ 'ecr:GetDownloadUrlForLayer',
+ 'ecr:DescribeRepositories',
+ 'ecr:DescribeImages',
+ 'ecr:BatchGetImage',
+ ],
+ })
+ );
+
+ // Add GetAuthorizationToken (it can not be bound to resources other than *)
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: ['*'],
+ actions: ['ecr:GetAuthorizationToken'],
+ })
+ );
+
+ // add permission to use Elastic Inference accelerator
+ if (props && props.endpointConfigProps) {
+ // Get the acceleratorType, if any
+ const acceleratorType = (props.endpointConfigProps
+ ?.productionVariants as sagemaker.CfnEndpointConfig.ProductionVariantProperty[])[0].acceleratorType;
+ if (acceleratorType !== undefined) {
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: ['*'],
+ actions: ['elastic-inference:Connect'],
+ })
+ );
+ }
+ }
+
+ // add kms permissions
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ // the kmsKeyId in the endpointConfigProps can be any of the following formats:
+ // Key ID: 1234abcd-12ab-34cd-56ef-1234567890ab
+ // Key ARN: arn:aws:kms:::key/1234abcd-12ab-34cd-56ef-1234567890ab
+ // Alias name: alias/ExampleAlias
+ // Alias name ARN: arn:aws:kms:::alias/ExampleAlias
+ // the key is used to encrypt/decrypt data captured by the Sagemaker endpoint and stored in S3 bucket
+ resources: [
+ `arn:${cdk.Aws.PARTITION}:kms:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:key/*`,
+ `arn:${cdk.Aws.PARTITION}:kms:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:alias/*`,
+ ],
+ actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
+ })
+ );
+
+ // Add S3 permissions to get Model artifact, put data capture files, etc.
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ actions: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject', 's3:ListBucket'],
+ resources: ['arn:aws:s3:::*'],
+ })
+ );
+ // Grant GetRole permissions to the Sagemaker service
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: [_role.roleArn],
+ actions: ['iam:GetRole'],
+ })
+ );
+
+ // Grant PassRole permissions to the Sagemaker service
+ _role.addToPolicy(
+ new iam.PolicyStatement({
+ resources: [_role.roleArn],
+ actions: ['iam:PassRole'],
+ conditions: {
+ StringLike: { 'iam:PassedToService': 'sagemaker.amazonaws.com' },
+ },
+ })
+ );
+
+ // Add CFN NAG uppress to allow for "Resource": "*" for ENI access in VPC,
+ // ECR authorization token for custom model images, and elastic inference
+ // Add CFN NAG for Complex Role because Sagmaker needs permissions to access several services
+ const roleDefualtPolicy = _role.node.tryFindChild('DefaultPolicy')?.node.findChild('Resource') as iam.CfnPolicy;
+ roleDefualtPolicy.cfnOptions.metadata = {
+ cfn_nag: {
+ rules_to_suppress: [
+ {
+ id: 'W12',
+ reason: `Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.`,
+ },
+ {
+ id: 'W76',
+ reason: 'Complex role becuase Sagemaker needs permissions to access several services',
+ },
+ ],
+ },
+ };
}
-export function buildSagemakerNotebook(scope: cdk.Construct, props: BuildSagemakerNotebookProps): [sagemaker.CfnNotebookInstance, ec2.IVpc?,
- ec2.SecurityGroup?] {
+export function buildSagemakerNotebook(
+ scope: cdk.Construct,
+ props: BuildSagemakerNotebookProps
+): [sagemaker.CfnNotebookInstance, ec2.IVpc?, ec2.SecurityGroup?] {
// Setup the notebook properties
let sagemakerNotebookProps;
let vpcInstance;
@@ -116,8 +229,10 @@ export function buildSagemakerNotebook(scope: cdk.Construct, props: BuildSagemak
// Conditional Sagemaker Notebook creation
if (!props.existingNotebookObj) {
- if ((props.sagemakerNotebookProps?.subnetId && props.sagemakerNotebookProps?.securityGroupIds === undefined) ||
- (props.sagemakerNotebookProps?.subnetId === undefined && props.sagemakerNotebookProps?.securityGroupIds)) {
+ if (
+ (props.sagemakerNotebookProps?.subnetId && props.sagemakerNotebookProps?.securityGroupIds === undefined) ||
+ (props.sagemakerNotebookProps?.subnetId === undefined && props.sagemakerNotebookProps?.securityGroupIds)
+ ) {
throw new Error('Must define both sagemakerNotebookProps.subnetId and sagemakerNotebookProps.securityGroupIds');
}
@@ -130,24 +245,36 @@ export function buildSagemakerNotebook(scope: cdk.Construct, props: BuildSagemak
}
if (props.deployInsideVpc === undefined || props.deployInsideVpc) {
- if (props.sagemakerNotebookProps?.subnetId === undefined && props.sagemakerNotebookProps?.securityGroupIds === undefined) {
+ if (
+ props.sagemakerNotebookProps?.subnetId === undefined &&
+ props.sagemakerNotebookProps?.securityGroupIds === undefined
+ ) {
vpcInstance = buildVpc(scope, {
- defaultVpcProps: DefaultPublicPrivateVpcProps()
+ defaultVpcProps: DefaultPublicPrivateVpcProps(),
});
- securityGroup = buildSecurityGroup(scope, "SecurityGroup", {
- vpc: vpcInstance,
- allowAllOutbound: false
- },
- [],
- [{ peer: ec2.Peer.anyIpv4(), connection: ec2.Port.tcp(443)}]
+ securityGroup = buildSecurityGroup(
+ scope,
+ 'SecurityGroup',
+ {
+ vpc: vpcInstance,
+ allowAllOutbound: false,
+ },
+ [],
+ [{ peer: ec2.Peer.anyIpv4(), connection: ec2.Port.tcp(443) }]
);
subnetId = vpcInstance.privateSubnets[0].subnetId;
- sagemakerNotebookProps = DefaultSagemakerNotebookProps(props.role.roleArn, kmsKeyId, subnetId, [securityGroup.securityGroupId]);
+ sagemakerNotebookProps = DefaultSagemakerNotebookProps(props.role.roleArn, kmsKeyId, subnetId, [
+ securityGroup.securityGroupId,
+ ]);
} else {
- sagemakerNotebookProps = DefaultSagemakerNotebookProps(props.role.roleArn, kmsKeyId,
- props.sagemakerNotebookProps?.subnetId, props.sagemakerNotebookProps?.securityGroupIds);
+ sagemakerNotebookProps = DefaultSagemakerNotebookProps(
+ props.role.roleArn,
+ kmsKeyId,
+ props.sagemakerNotebookProps?.subnetId,
+ props.sagemakerNotebookProps?.securityGroupIds
+ );
}
} else {
sagemakerNotebookProps = DefaultSagemakerNotebookProps(props.role.roleArn, kmsKeyId);
@@ -158,7 +285,11 @@ export function buildSagemakerNotebook(scope: cdk.Construct, props: BuildSagemak
}
// Create the notebook
- const sagemakerInstance: sagemaker.CfnNotebookInstance = new sagemaker.CfnNotebookInstance(scope, "SagemakerNotebook", sagemakerNotebookProps);
+ const sagemakerInstance: sagemaker.CfnNotebookInstance = new sagemaker.CfnNotebookInstance(
+ scope,
+ 'SagemakerNotebook',
+ sagemakerNotebookProps
+ );
if (vpcInstance) {
return [sagemakerInstance, vpcInstance, securityGroup];
} else {
@@ -169,3 +300,217 @@ export function buildSagemakerNotebook(scope: cdk.Construct, props: BuildSagemak
return [props.existingNotebookObj];
}
}
+
+export interface BuildSagemakerEndpointProps {
+ /**
+ * Existing Sagemaker Enpoint object, if this is set then the modelProps, endpointConfigProps, and endpointProps are ignored
+ *
+ * @default - None
+ */
+ readonly existingSagemakerEndpointObj?: sagemaker.CfnEndpoint;
+ /**
+ * User provided props to create Sagemaker Model
+ *
+ * @default - None
+ */
+ readonly modelProps?: sagemaker.CfnModelProps | any;
+ /**
+ * User provided props to create Sagemaker Endpoint Configuration
+ *
+ * @default - None
+ */
+ readonly endpointConfigProps?: sagemaker.CfnEndpointConfigProps;
+ /**
+ * User provided props to create Sagemaker Endpoint
+ *
+ * @default - None
+ */
+ readonly endpointProps?: sagemaker.CfnEndpointProps;
+ /**
+ * A VPC where the Sagemaker Endpoint will be placed
+ *
+ * @default - None
+ */
+ readonly vpc?: ec2.IVpc;
+}
+
+export function BuildSagemakerEndpoint(
+ scope: cdk.Construct,
+ props: BuildSagemakerEndpointProps
+): [sagemaker.CfnEndpoint, sagemaker.CfnEndpointConfig?, sagemaker.CfnModel?] {
+ /** Conditional Sagemaker endpoint creation */
+ if (!props.existingSagemakerEndpointObj) {
+ if (props.modelProps) {
+ /** return [endpoint, endpointConfig, model] */
+ return deploySagemakerEndpoint(scope, props);
+ } else {
+ throw Error('Either existingSagemakerEndpointObj or at least modelProps is required');
+ }
+ } else {
+ /** Otherwise, return [endpoint] */
+ return [props.existingSagemakerEndpointObj];
+ }
+}
+
+export function deploySagemakerEndpoint(
+ scope: cdk.Construct,
+ props: BuildSagemakerEndpointProps
+): [sagemaker.CfnEndpoint, sagemaker.CfnEndpointConfig?, sagemaker.CfnModel?] {
+ let model: sagemaker.CfnModel;
+ let endpointConfig: sagemaker.CfnEndpointConfig;
+ let endpoint: sagemaker.CfnEndpoint;
+ let sagemakerRole: iam.Role;
+
+ // Create Sagemaker's model, endpointConfig, and endpoint
+ if (props.modelProps) {
+ // Check if the client has provided executionRoleArn
+ if (props.modelProps.executionRoleArn) {
+ sagemakerRole = iam.Role.fromRoleArn(
+ scope,
+ 'SagemakerRoleCustomer',
+ props.modelProps.executionRoleArn
+ ) as iam.Role;
+ } else {
+ // Create the Sagemaker Role
+ sagemakerRole = new iam.Role(scope, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
+ });
+ // Add required permissions
+ addPermissions(sagemakerRole, props);
+ }
+
+ // Create Sagemaker Model
+ model = createSagemakerModel(scope, props.modelProps, sagemakerRole, props.vpc);
+ // Create Sagemaker EndpointConfig
+ endpointConfig = createSagemakerEndpointConfig(scope, model.attrModelName, props.endpointConfigProps);
+ // Add dependency on model
+ endpointConfig.addDependsOn(model);
+ // Create Sagemaker Endpoint
+ endpoint = createSagemakerEndpoint(scope, endpointConfig.attrEndpointConfigName, props.endpointProps);
+ // Add dependency on EndpointConfig
+ endpoint.addDependsOn(endpointConfig);
+
+ return [endpoint, endpointConfig, model];
+ } else {
+ throw Error('You need to provide at least modelProps to create Sagemaker Endpoint');
+ }
+}
+
+export function createSagemakerModel(
+ scope: cdk.Construct,
+ modelProps: sagemaker.CfnModelProps,
+ role: iam.Role,
+ vpc?: ec2.IVpc
+): sagemaker.CfnModel {
+ let finalModelProps: sagemaker.CfnModelProps;
+ let primaryContainer: sagemaker.CfnModel.ContainerDefinitionProperty;
+ let vpcConfig: sagemaker.CfnModel.VpcConfigProperty | undefined;
+ let model: sagemaker.CfnModel;
+
+ if (vpc) {
+ const modelDefaultSecurityGroup = new ec2.SecurityGroup(scope, 'ReplaceModelDefaultSecurityGroup', {
+ vpc,
+ allowAllOutbound: true,
+ });
+
+ // Allow https traffic from within the VPC
+ modelDefaultSecurityGroup.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(443));
+
+ const cfnSecurityGroup = modelDefaultSecurityGroup.node.findChild('Resource') as ec2.CfnSecurityGroup;
+ cfnSecurityGroup.cfnOptions.metadata = {
+ cfn_nag: {
+ rules_to_suppress: [
+ {
+ id: 'W5',
+ reason: 'Egress of 0.0.0.0/0 is default and generally considered OK',
+ },
+ {
+ id: 'W40',
+ reason: 'Egress IPProtocol of -1 is default and generally considered OK',
+ },
+ ],
+ },
+ };
+
+ // Throw an error if the VPC does not contain private or isolated subnets
+ if (vpc.privateSubnets.length === 0 && vpc.isolatedSubnets.length === 0) {
+ throw Error('VPC must contain private or isolated subnets to deploy the Sagemaker endpoint in a vpc');
+ }
+
+ vpcConfig = {
+ // default SubnetType.PRIVATE (or ISOLATED or PUBLIC if there are no PRIVATE subnets)
+ // So, private subnets will be used if provided by customer. Otherwise, use the default isolated subnets,
+ subnets: vpc.selectSubnets({
+ onePerAz: true,
+ }).subnetIds,
+ securityGroupIds: [modelDefaultSecurityGroup.securityGroupId],
+ };
+ }
+
+ if (modelProps.primaryContainer) {
+ // Get user provided Model's primary container
+ primaryContainer = modelProps.primaryContainer as sagemaker.CfnModel.ContainerDefinitionProperty;
+ // Get default Model props
+ finalModelProps = DefaultSagemakerModelProps(role.roleArn, primaryContainer, vpcConfig);
+ // Override default model properties
+ finalModelProps = overrideProps(finalModelProps, modelProps);
+
+ // Create the Sagemaker's Model
+ model = new sagemaker.CfnModel(scope, 'SagemakerModel', finalModelProps);
+ // Add dependency on the Sagemaker's role
+ model.node.addDependency(role);
+
+ return model;
+ } else {
+ throw Error('You need to provide at least primaryContainer to create Sagemaker Model');
+ }
+}
+
+export function createSagemakerEndpointConfig(
+ scope: cdk.Construct,
+ modelName: string,
+ endpointConfigProps?: sagemaker.CfnEndpointConfigProps
+): sagemaker.CfnEndpointConfig {
+ let finalEndpointConfigProps: sagemaker.CfnEndpointConfigProps;
+ let kmsKeyId: string;
+ let endpointConfig: sagemaker.CfnEndpointConfig;
+
+ // Create encryption key if one is not provided
+ if (endpointConfigProps && endpointConfigProps.kmsKeyId) {
+ kmsKeyId = endpointConfigProps.kmsKeyId;
+ } else {
+ kmsKeyId = buildEncryptionKey(scope).keyId;
+ }
+ // Get default EndpointConfig props
+ finalEndpointConfigProps = DefaultSagemakerEndpointConfigProps(modelName, kmsKeyId);
+ // Overwrite default EndpointConfig properties
+ if (endpointConfigProps) {
+ finalEndpointConfigProps = overrideProps(finalEndpointConfigProps, endpointConfigProps);
+ }
+
+ // Create the Sagemaker's EndpointConfig
+ endpointConfig = new sagemaker.CfnEndpointConfig(scope, 'SagemakerEndpointConfig', finalEndpointConfigProps);
+
+ return endpointConfig;
+}
+
+export function createSagemakerEndpoint(
+ scope: cdk.Construct,
+ endpointConfigName: string,
+ endpointProps?: sagemaker.CfnEndpointProps
+): sagemaker.CfnEndpoint {
+ let finalEndpointProps: sagemaker.CfnEndpointProps;
+ let endpoint: sagemaker.CfnEndpoint;
+
+ // Get default Endpoint props
+ finalEndpointProps = DefaultSagemakerEndpointProps(endpointConfigName);
+ // Overwrite default Endpoint properties
+ if (endpointProps) {
+ finalEndpointProps = overrideProps(finalEndpointProps, endpointProps);
+ }
+
+ // Create the Sagemaker's Endpoint
+ endpoint = new sagemaker.CfnEndpoint(scope, 'SagemakerEndpoint', finalEndpointProps);
+
+ return endpoint;
+}
diff --git a/source/patterns/@aws-solutions-constructs/core/lib/vpc-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/vpc-helper.ts
index de1dbcfd8..9775894b3 100644
--- a/source/patterns/@aws-solutions-constructs/core/lib/vpc-helper.ts
+++ b/source/patterns/@aws-solutions-constructs/core/lib/vpc-helper.ts
@@ -94,6 +94,7 @@ export enum ServiceEndpointTypes {
SQS = "SQS",
S3 = "S3",
STEPFUNCTIONS = "STEPFUNCTIONS",
+ SAGEMAKER_RUNTIME = "SAGEMAKER_RUNTIME",
}
enum EndpointTypes {
@@ -129,6 +130,11 @@ const endpointSettings: EndpointDefinition[] = [
endpointType: EndpointTypes.INTERFACE,
endpointInterfaceService: ec2.InterfaceVpcEndpointAwsService.SQS,
},
+ {
+ endpointName: ServiceEndpointTypes.SAGEMAKER_RUNTIME,
+ endpointType: EndpointTypes.INTERFACE,
+ endpointInterfaceService: ec2.InterfaceVpcEndpointAwsService.SAGEMAKER_RUNTIME,
+ },
];
export function AddAwsServiceEndpoint(
diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sagemaker-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sagemaker-helper.test.js.snap
index d87e4c22a..cdc8b91ab 100644
--- a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sagemaker-helper.test.js.snap
+++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sagemaker-helper.test.js.snap
@@ -1,12 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Test deployment w/o VPC 1`] = `
+exports[`Test deployment of Sagemaker Inference Endpoint with properties overwrite 1`] = `
Object {
"Resources": Object {
- "EncryptionKey1B843E66": Object {
+ "MyEndpointConfigEncryptionKey42E27FFC": Object {
"DeletionPolicy": "Retain",
"Properties": Object {
- "EnableKeyRotation": true,
"KeyPolicy": Object {
"Statement": Array [
Object {
@@ -55,20 +54,194 @@ Object {
"Type": "AWS::KMS::Key",
"UpdateReplacePolicy": "Retain",
},
- "SagemakerNotebook": Object {
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
"Properties": Object {
- "InstanceType": "ml.t2.medium",
+ "GroupDescription": "Default/ReplaceEndpointDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "SecurityGroupIngress": Array [
+ Object {
+ "CidrIp": Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ "Description": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "from ",
+ Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ ":443",
+ ],
+ ],
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "ReplaceModelDefaultSecurityGroup38936A39": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/ReplaceModelDefaultSecurityGroup",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "SecurityGroupIngress": Array [
+ Object {
+ "CidrIp": Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ "Description": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "from ",
+ Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ ":443",
+ ],
+ ],
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "SagemakerEndpoint": Object {
+ "DependsOn": Array [
+ "SagemakerEndpointConfig",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": "linear-learner-endpoint-config",
+ "EndpointName": "linear-learner-endpoint",
+ },
+ "Type": "AWS::SageMaker::Endpoint",
+ },
+ "SagemakerEndpointConfig": Object {
+ "DependsOn": Array [
+ "SagemakerModel",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": "linear-learner-endpoint-config",
"KmsKeyId": Object {
- "Ref": "EncryptionKey1B843E66",
+ "Fn::GetAtt": Array [
+ "MyEndpointConfigEncryptionKey42E27FFC",
+ "Arn",
+ ],
},
- "RoleArn": Object {
+ "ProductionVariants": Array [
+ Object {
+ "AcceleratorType": "ml.eia2.medium",
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.large",
+ "ModelName": "linear-learner-model",
+ "VariantName": "AllTraffic",
+ },
+ ],
+ },
+ "Type": "AWS::SageMaker::EndpointConfig",
+ },
+ "SagemakerModel": Object {
+ "DependsOn": Array [
+ "SagemakerRoleDefaultPolicy9DD21C3C",
+ "SagemakerRole5FDB64E1",
+ ],
+ "Properties": Object {
+ "ExecutionRoleArn": Object {
"Fn::GetAtt": Array [
"SagemakerRole5FDB64E1",
"Arn",
],
},
+ "ModelName": "linear-learner-model",
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
+ },
+ "VpcConfig": Object {
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "ReplaceModelDefaultSecurityGroup38936A39",
+ "GroupId",
+ ],
+ },
+ ],
+ "Subnets": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
+ },
+ ],
+ },
},
- "Type": "AWS::SageMaker::NotebookInstance",
+ "Type": "AWS::SageMaker::Model",
},
"SagemakerRole5FDB64E1": Object {
"Properties": Object {
@@ -88,6 +261,20 @@ Object {
"Type": "AWS::IAM::Role",
},
"SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
"Properties": Object {
"PolicyDocument": Object {
"Statement": Array [
@@ -158,6 +345,125 @@ Object {
],
},
},
+ Object {
+ "Action": Array [
+ "ec2:CreateNetworkInterface",
+ "ec2:CreateNetworkInterfacePermission",
+ "ec2:DeleteNetworkInterface",
+ "ec2:DeleteNetworkInterfacePermission",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses",
+ "ec2:DescribeVpcs",
+ "ec2:DescribeDhcpOptions",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeSecurityGroups",
+ ],
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": "elastic-inference:Connect",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
Object {
"Action": "iam:GetRole",
"Effect": "Allow",
@@ -195,201 +501,94 @@ Object {
},
"Type": "AWS::IAM::Policy",
},
- },
-}
-`;
-
-exports[`Test deployment with VPC 1`] = `
-Object {
- "Resources": Object {
- "EncryptionKey1B843E66": Object {
- "DeletionPolicy": "Retain",
+ "Vpc8378EB38": Object {
"Properties": Object {
- "EnableKeyRotation": true,
- "KeyPolicy": Object {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::VPC",
+ },
+ "VpcFlowLog8FF33A73": Object {
+ "Properties": Object {
+ "DeliverLogsPermissionArn": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": Object {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9",
+ },
+ "ResourceId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ "ResourceType": "VPC",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ "TrafficType": "ALL",
+ },
+ "Type": "AWS::EC2::FlowLog",
+ },
+ "VpcFlowLogIAMRole6A475D41": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
"Statement": Array [
Object {
- "Action": Array [
- "kms:Create*",
- "kms:Describe*",
- "kms:Enable*",
- "kms:List*",
- "kms:Put*",
- "kms:Update*",
- "kms:Revoke*",
- "kms:Disable*",
- "kms:Get*",
- "kms:Delete*",
- "kms:ScheduleKeyDeletion",
- "kms:CancelKeyDeletion",
- "kms:GenerateDataKey",
- "kms:TagResource",
- "kms:UntagResource",
- ],
+ "Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": Object {
- "AWS": Object {
- "Fn::Join": Array [
- "",
- Array [
- "arn:",
- Object {
- "Ref": "AWS::Partition",
- },
- ":iam::",
- Object {
- "Ref": "AWS::AccountId",
- },
- ":root",
- ],
- ],
- },
+ "Service": "vpc-flow-logs.amazonaws.com",
},
- "Resource": "*",
},
],
"Version": "2012-10-17",
},
- },
- "Type": "AWS::KMS::Key",
- "UpdateReplacePolicy": "Retain",
- },
- "SagemakerNotebook": Object {
- "Properties": Object {
- "DirectInternetAccess": "Disabled",
- "InstanceType": "ml.t2.medium",
- "KmsKeyId": Object {
- "Ref": "EncryptionKey1B843E66",
- },
- "RoleArn": Object {
- "Fn::GetAtt": Array [
- "SagemakerRole5FDB64E1",
- "Arn",
- ],
- },
- "SecurityGroupIds": Array [
+ "Tags": Array [
Object {
- "Fn::GetAtt": Array [
- "SecurityGroupsecuritygroup00653C55",
- "GroupId",
- ],
+ "Key": "Name",
+ "Value": "Default/Vpc",
},
],
- "SubnetId": Object {
- "Ref": "VpcPrivateSubnet1Subnet536B997A",
- },
- },
- "Type": "AWS::SageMaker::NotebookInstance",
- },
- "SagemakerRole5FDB64E1": Object {
- "Properties": Object {
- "AssumeRolePolicyDocument": Object {
- "Statement": Array [
- Object {
- "Action": "sts:AssumeRole",
- "Effect": "Allow",
- "Principal": Object {
- "Service": "sagemaker.amazonaws.com",
- },
- },
- ],
- "Version": "2012-10-17",
- },
},
"Type": "AWS::IAM::Role",
},
- "SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": Object {
"Properties": Object {
"PolicyDocument": Object {
"Statement": Array [
Object {
"Action": Array [
- "sagemaker:CreateTrainingJob",
- "sagemaker:DescribeTrainingJob",
- "sagemaker:CreateModel",
- "sagemaker:DescribeModel",
- "sagemaker:DeleteModel",
- "sagemaker:CreateEndpoint",
- "sagemaker:CreateEndpointConfig",
- "sagemaker:DescribeEndpoint",
- "sagemaker:DescribeEndpointConfig",
- "sagemaker:DeleteEndpoint",
- "sagemaker:DeleteEndpointConfig",
- "sagemaker:InvokeEndpoint",
- ],
- "Effect": "Allow",
- "Resource": Object {
- "Fn::Join": Array [
- "",
- Array [
- "arn:",
- Object {
- "Ref": "AWS::Partition",
- },
- ":sagemaker:",
- Object {
- "Ref": "AWS::Region",
- },
- ":",
- Object {
- "Ref": "AWS::AccountId",
- },
- ":*",
- ],
- ],
- },
- },
- Object {
- "Action": Array [
- "logs:CreateLogGroup",
"logs:CreateLogStream",
- "logs:DescribeLogStreams",
- "logs:GetLogEvents",
"logs:PutLogEvents",
+ "logs:DescribeLogStreams",
],
"Effect": "Allow",
- "Resource": Object {
- "Fn::Join": Array [
- "",
- Array [
- "arn:",
- Object {
- "Ref": "AWS::Partition",
- },
- ":logs:",
- Object {
- "Ref": "AWS::Region",
- },
- ":",
- Object {
- "Ref": "AWS::AccountId",
- },
- ":log-group:/aws/sagemaker/*",
- ],
- ],
- },
- },
- Object {
- "Action": "iam:GetRole",
- "Effect": "Allow",
"Resource": Object {
"Fn::GetAtt": Array [
- "SagemakerRole5FDB64E1",
+ "VpcFlowLogLogGroup7B5C56B9",
"Arn",
],
},
},
Object {
"Action": "iam:PassRole",
- "Condition": Object {
- "StringLike": Object {
- "iam:PassedToService": "sagemaker.amazonaws.com",
- },
- },
"Effect": "Allow",
"Resource": Object {
"Fn::GetAtt": Array [
- "SagemakerRole5FDB64E1",
+ "VpcFlowLogIAMRole6A475D41",
"Arn",
],
},
@@ -397,241 +596,149 @@ Object {
],
"Version": "2012-10-17",
},
- "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
"Roles": Array [
Object {
- "Ref": "SagemakerRole5FDB64E1",
+ "Ref": "VpcFlowLogIAMRole6A475D41",
},
],
},
"Type": "AWS::IAM::Policy",
},
- "SecurityGroupsecuritygroup00653C55": Object {
+ "VpcFlowLogLogGroup7B5C56B9": Object {
+ "DeletionPolicy": "Retain",
"Metadata": Object {
"cfn_nag": Object {
"rules_to_suppress": Array [
Object {
- "id": "W5",
- "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
- },
- Object {
- "id": "W40",
- "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)",
},
],
},
},
"Properties": Object {
- "GroupDescription": "Default/SecurityGroup-security-group",
- "SecurityGroupEgress": Array [
+ "RetentionInDays": 731,
+ },
+ "Type": "AWS::Logs::LogGroup",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "VpcS3A5408339": Object {
+ "Properties": Object {
+ "RouteTableIds": Array [
Object {
- "CidrIp": "0.0.0.0/0",
- "Description": "from 0.0.0.0/0:443",
- "FromPort": 443,
- "IpProtocol": "tcp",
- "ToPort": 443,
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764",
},
],
+ "ServiceName": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "com.amazonaws.",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".s3",
+ ],
+ ],
+ },
+ "VpcEndpointType": "Gateway",
"VpcId": Object {
"Ref": "Vpc8378EB38",
},
},
- "Type": "AWS::EC2::SecurityGroup",
+ "Type": "AWS::EC2::VPCEndpoint",
},
- "Vpc8378EB38": Object {
+ "VpcSAGEMAKERRUNTIME337E125A": Object {
"Properties": Object {
- "CidrBlock": "10.0.0.0/16",
- "EnableDnsHostnames": true,
- "EnableDnsSupport": true,
- "InstanceTenancy": "default",
- "Tags": Array [
+ "PrivateDnsEnabled": true,
+ "SecurityGroupIds": Array [
Object {
- "Key": "Name",
- "Value": "Default/Vpc",
+ "Fn::GetAtt": Array [
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF",
+ "GroupId",
+ ],
+ },
+ ],
+ "ServiceName": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "com.amazonaws.",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".sagemaker.runtime",
+ ],
+ ],
+ },
+ "SubnetIds": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
},
],
+ "VpcEndpointType": "Interface",
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
},
- "Type": "AWS::EC2::VPC",
+ "Type": "AWS::EC2::VPCEndpoint",
},
- "VpcFlowLog8FF33A73": Object {
+ "VpcisolatedSubnet1RouteTableAssociationD259E31A": Object {
"Properties": Object {
- "DeliverLogsPermissionArn": Object {
- "Fn::GetAtt": Array [
- "VpcFlowLogIAMRole6A475D41",
- "Arn",
- ],
- },
- "LogDestinationType": "cloud-watch-logs",
- "LogGroupName": Object {
- "Ref": "VpcFlowLogLogGroup7B5C56B9",
+ "RouteTableId": Object {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B",
},
- "ResourceId": Object {
- "Ref": "Vpc8378EB38",
+ "SubnetId": Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
},
- "ResourceType": "VPC",
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcisolatedSubnet1RouteTableE442650B": Object {
+ "Properties": Object {
"Tags": Array [
Object {
"Key": "Name",
- "Value": "Default/Vpc",
+ "Value": "Default/Vpc/isolatedSubnet1",
},
],
- "TrafficType": "ALL",
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
},
- "Type": "AWS::EC2::FlowLog",
+ "Type": "AWS::EC2::RouteTable",
},
- "VpcFlowLogIAMRole6A475D41": Object {
+ "VpcisolatedSubnet1SubnetE62B1B9B": Object {
"Properties": Object {
- "AssumeRolePolicyDocument": Object {
- "Statement": Array [
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
Object {
- "Action": "sts:AssumeRole",
- "Effect": "Allow",
- "Principal": Object {
- "Service": "vpc-flow-logs.amazonaws.com",
- },
+ "Fn::GetAZs": "",
},
],
- "Version": "2012-10-17",
},
- "Tags": Array [
- Object {
- "Key": "Name",
- "Value": "Default/Vpc",
- },
- ],
- },
- "Type": "AWS::IAM::Role",
- },
- "VpcFlowLogIAMRoleDefaultPolicy406FB995": Object {
- "Properties": Object {
- "PolicyDocument": Object {
- "Statement": Array [
- Object {
- "Action": Array [
- "logs:CreateLogStream",
- "logs:PutLogEvents",
- "logs:DescribeLogStreams",
- ],
- "Effect": "Allow",
- "Resource": Object {
- "Fn::GetAtt": Array [
- "VpcFlowLogLogGroup7B5C56B9",
- "Arn",
- ],
- },
- },
- Object {
- "Action": "iam:PassRole",
- "Effect": "Allow",
- "Resource": Object {
- "Fn::GetAtt": Array [
- "VpcFlowLogIAMRole6A475D41",
- "Arn",
- ],
- },
- },
- ],
- "Version": "2012-10-17",
- },
- "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
- "Roles": Array [
- Object {
- "Ref": "VpcFlowLogIAMRole6A475D41",
- },
- ],
- },
- "Type": "AWS::IAM::Policy",
- },
- "VpcFlowLogLogGroup7B5C56B9": Object {
- "DeletionPolicy": "Retain",
- "Metadata": Object {
- "cfn_nag": Object {
- "rules_to_suppress": Array [
- Object {
- "id": "W84",
- "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)",
- },
- ],
- },
- },
- "Properties": Object {
- "RetentionInDays": 731,
- },
- "Type": "AWS::Logs::LogGroup",
- "UpdateReplacePolicy": "Retain",
- },
- "VpcIGWD7BA715C": Object {
- "Properties": Object {
- "Tags": Array [
- Object {
- "Key": "Name",
- "Value": "Default/Vpc",
- },
- ],
- },
- "Type": "AWS::EC2::InternetGateway",
- },
- "VpcPrivateSubnet1DefaultRouteBE02A9ED": Object {
- "Properties": Object {
- "DestinationCidrBlock": "0.0.0.0/0",
- "NatGatewayId": Object {
- "Ref": "VpcPublicSubnet1NATGateway4D7517AA",
- },
- "RouteTableId": Object {
- "Ref": "VpcPrivateSubnet1RouteTableB2C5B500",
- },
- },
- "Type": "AWS::EC2::Route",
- },
- "VpcPrivateSubnet1RouteTableAssociation70C59FA6": Object {
- "Properties": Object {
- "RouteTableId": Object {
- "Ref": "VpcPrivateSubnet1RouteTableB2C5B500",
- },
- "SubnetId": Object {
- "Ref": "VpcPrivateSubnet1Subnet536B997A",
- },
- },
- "Type": "AWS::EC2::SubnetRouteTableAssociation",
- },
- "VpcPrivateSubnet1RouteTableB2C5B500": Object {
- "Properties": Object {
- "Tags": Array [
- Object {
- "Key": "Name",
- "Value": "Default/Vpc/PrivateSubnet1",
- },
- ],
- "VpcId": Object {
- "Ref": "Vpc8378EB38",
- },
- },
- "Type": "AWS::EC2::RouteTable",
- },
- "VpcPrivateSubnet1Subnet536B997A": Object {
- "Properties": Object {
- "AvailabilityZone": Object {
- "Fn::Select": Array [
- 0,
- Object {
- "Fn::GetAZs": "",
- },
- ],
- },
- "CidrBlock": "10.0.128.0/18",
+ "CidrBlock": "10.0.0.0/18",
"MapPublicIpOnLaunch": false,
"Tags": Array [
Object {
"Key": "aws-cdk:subnet-name",
- "Value": "Private",
+ "Value": "isolated",
},
Object {
"Key": "aws-cdk:subnet-type",
- "Value": "Private",
+ "Value": "Isolated",
},
Object {
"Key": "Name",
- "Value": "Default/Vpc/PrivateSubnet1",
+ "Value": "Default/Vpc/isolatedSubnet1",
},
],
"VpcId": Object {
@@ -640,24 +747,12 @@ Object {
},
"Type": "AWS::EC2::Subnet",
},
- "VpcPrivateSubnet2DefaultRoute060D2087": Object {
- "Properties": Object {
- "DestinationCidrBlock": "0.0.0.0/0",
- "NatGatewayId": Object {
- "Ref": "VpcPublicSubnet2NATGateway9182C01D",
- },
- "RouteTableId": Object {
- "Ref": "VpcPrivateSubnet2RouteTableA678073B",
- },
- },
- "Type": "AWS::EC2::Route",
- },
- "VpcPrivateSubnet2RouteTableA678073B": Object {
+ "VpcisolatedSubnet2RouteTable334F9764": Object {
"Properties": Object {
"Tags": Array [
Object {
"Key": "Name",
- "Value": "Default/Vpc/PrivateSubnet2",
+ "Value": "Default/Vpc/isolatedSubnet2",
},
],
"VpcId": Object {
@@ -666,18 +761,18 @@ Object {
},
"Type": "AWS::EC2::RouteTable",
},
- "VpcPrivateSubnet2RouteTableAssociationA89CAD56": Object {
+ "VpcisolatedSubnet2RouteTableAssociation25A4716F": Object {
"Properties": Object {
"RouteTableId": Object {
- "Ref": "VpcPrivateSubnet2RouteTableA678073B",
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764",
},
"SubnetId": Object {
- "Ref": "VpcPrivateSubnet2Subnet3788AAA1",
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
},
},
"Type": "AWS::EC2::SubnetRouteTableAssociation",
},
- "VpcPrivateSubnet2Subnet3788AAA1": Object {
+ "VpcisolatedSubnet2Subnet39217055": Object {
"Properties": Object {
"AvailabilityZone": Object {
"Fn::Select": Array [
@@ -687,20 +782,20 @@ Object {
},
],
},
- "CidrBlock": "10.0.192.0/18",
+ "CidrBlock": "10.0.64.0/18",
"MapPublicIpOnLaunch": false,
"Tags": Array [
Object {
"Key": "aws-cdk:subnet-name",
- "Value": "Private",
+ "Value": "isolated",
},
Object {
"Key": "aws-cdk:subnet-type",
- "Value": "Private",
+ "Value": "Isolated",
},
Object {
"Key": "Name",
- "Value": "Default/Vpc/PrivateSubnet2",
+ "Value": "Default/Vpc/isolatedSubnet2",
},
],
"VpcId": Object {
@@ -709,203 +804,3317 @@ Object {
},
"Type": "AWS::EC2::Subnet",
},
- "VpcPublicSubnet1DefaultRoute3DA9E72A": Object {
+ },
+}
+`;
+
+exports[`Test deployment of existing Sagemaker Endpoint 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "SagemakerEndpoint": Object {
"DependsOn": Array [
- "VpcVPCGWBF912B6E",
+ "SagemakerEndpointConfig",
],
"Properties": Object {
- "DestinationCidrBlock": "0.0.0.0/0",
- "GatewayId": Object {
- "Ref": "VpcIGWD7BA715C",
- },
- "RouteTableId": Object {
- "Ref": "VpcPublicSubnet1RouteTable6C95E38E",
+ "EndpointConfigName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerEndpointConfig",
+ "EndpointConfigName",
+ ],
},
},
- "Type": "AWS::EC2::Route",
+ "Type": "AWS::SageMaker::Endpoint",
},
- "VpcPublicSubnet1EIPD7E02669": Object {
+ "SagemakerEndpointConfig": Object {
+ "DependsOn": Array [
+ "SagemakerModel",
+ ],
"Properties": Object {
- "Domain": "vpc",
- "Tags": Array [
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
+ },
+ "ProductionVariants": Array [
Object {
- "Key": "Name",
- "Value": "Default/Vpc/PublicSubnet1",
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerModel",
+ "ModelName",
+ ],
+ },
+ "VariantName": "AllTraffic",
},
],
},
- "Type": "AWS::EC2::EIP",
+ "Type": "AWS::SageMaker::EndpointConfig",
},
- "VpcPublicSubnet1NATGateway4D7517AA": Object {
+ "SagemakerModel": Object {
+ "DependsOn": Array [
+ "SagemakerRoleDefaultPolicy9DD21C3C",
+ "SagemakerRole5FDB64E1",
+ ],
"Properties": Object {
- "AllocationId": Object {
+ "ExecutionRoleArn": Object {
"Fn::GetAtt": Array [
- "VpcPublicSubnet1EIPD7E02669",
- "AllocationId",
+ "SagemakerRole5FDB64E1",
+ "Arn",
],
},
- "SubnetId": Object {
- "Ref": "VpcPublicSubnet1Subnet5C2D37C4",
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
},
- "Tags": Array [
- Object {
- "Key": "Name",
- "Value": "Default/Vpc/PublicSubnet1",
- },
- ],
},
- "Type": "AWS::EC2::NatGateway",
+ "Type": "AWS::SageMaker::Model",
},
- "VpcPublicSubnet1RouteTable6C95E38E": Object {
+ "SagemakerRole5FDB64E1": Object {
"Properties": Object {
- "Tags": Array [
- Object {
- "Key": "Name",
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": Array [
+ Object {
+ "Ref": "SagemakerRole5FDB64E1",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ },
+}
+`;
+
+exports[`Test deployment of sagemaker endpoint with a customer provided role 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "SagemakerEndpoint": Object {
+ "DependsOn": Array [
+ "SagemakerEndpointConfig",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerEndpointConfig",
+ "EndpointConfigName",
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Endpoint",
+ },
+ "SagemakerEndpointConfig": Object {
+ "DependsOn": Array [
+ "SagemakerModel",
+ ],
+ "Properties": Object {
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
+ },
+ "ProductionVariants": Array [
+ Object {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerModel",
+ "ModelName",
+ ],
+ },
+ "VariantName": "AllTraffic",
+ },
+ ],
+ },
+ "Type": "AWS::SageMaker::EndpointConfig",
+ },
+ "SagemakerModel": Object {
+ "Properties": Object {
+ "ExecutionRoleArn": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
+ },
+ },
+ "Type": "AWS::SageMaker::Model",
+ },
+ "SagemakerRole5FDB64E1": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "ManagedPolicyArns": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::aws:policy/AmazonSageMakerFullAccess",
+ ],
+ ],
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ },
+}
+`;
+
+exports[`Test deployment w/o VPC 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "SagemakerNotebook": Object {
+ "Properties": Object {
+ "InstanceType": "ml.t2.medium",
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
+ },
+ "RoleArn": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::NotebookInstance",
+ },
+ "SagemakerRole5FDB64E1": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": Array [
+ Object {
+ "Ref": "SagemakerRole5FDB64E1",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ },
+}
+`;
+
+exports[`Test deployment with VPC 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "SagemakerNotebook": Object {
+ "Properties": Object {
+ "DirectInternetAccess": "Disabled",
+ "InstanceType": "ml.t2.medium",
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
+ },
+ "RoleArn": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "SecurityGroupsecuritygroup00653C55",
+ "GroupId",
+ ],
+ },
+ ],
+ "SubnetId": Object {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A",
+ },
+ },
+ "Type": "AWS::SageMaker::NotebookInstance",
+ },
+ "SagemakerRole5FDB64E1": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": Array [
+ Object {
+ "Ref": "SagemakerRole5FDB64E1",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "SecurityGroupsecuritygroup00653C55": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/SecurityGroup-security-group",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "from 0.0.0.0/0:443",
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "Vpc8378EB38": Object {
+ "Properties": Object {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::VPC",
+ },
+ "VpcFlowLog8FF33A73": Object {
+ "Properties": Object {
+ "DeliverLogsPermissionArn": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": Object {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9",
+ },
+ "ResourceId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ "ResourceType": "VPC",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ "TrafficType": "ALL",
+ },
+ "Type": "AWS::EC2::FlowLog",
+ },
+ "VpcFlowLogIAMRole6A475D41": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "vpc-flow-logs.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": Object {
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": Array [
+ Object {
+ "Ref": "VpcFlowLogIAMRole6A475D41",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "VpcFlowLogLogGroup7B5C56B9": Object {
+ "DeletionPolicy": "Retain",
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "RetentionInDays": 731,
+ },
+ "Type": "AWS::Logs::LogGroup",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "VpcIGWD7BA715C": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::InternetGateway",
+ },
+ "VpcPrivateSubnet1DefaultRouteBE02A9ED": Object {
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": Object {
+ "Ref": "VpcPublicSubnet1NATGateway4D7517AA",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPrivateSubnet1RouteTableAssociation70C59FA6": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPrivateSubnet1RouteTableB2C5B500": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPrivateSubnet1Subnet536B997A": Object {
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.128.0/18",
+ "MapPublicIpOnLaunch": false,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcPrivateSubnet2DefaultRoute060D2087": Object {
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": Object {
+ "Ref": "VpcPublicSubnet2NATGateway9182C01D",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPrivateSubnet2RouteTableA678073B": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPrivateSubnet2RouteTableAssociationA89CAD56": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPrivateSubnet2Subnet3788AAA1",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPrivateSubnet2Subnet3788AAA1": Object {
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.192.0/18",
+ "MapPublicIpOnLaunch": false,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcPublicSubnet1DefaultRoute3DA9E72A": Object {
+ "DependsOn": Array [
+ "VpcVPCGWBF912B6E",
+ ],
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPublicSubnet1EIPD7E02669": Object {
+ "Properties": Object {
+ "Domain": "vpc",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::EIP",
+ },
+ "VpcPublicSubnet1NATGateway4D7517AA": Object {
+ "Properties": Object {
+ "AllocationId": Object {
+ "Fn::GetAtt": Array [
+ "VpcPublicSubnet1EIPD7E02669",
+ "AllocationId",
+ ],
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet1Subnet5C2D37C4",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::NatGateway",
+ },
+ "VpcPublicSubnet1RouteTable6C95E38E": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPublicSubnet1RouteTableAssociation97140677": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet1Subnet5C2D37C4",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPublicSubnet1Subnet5C2D37C4": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.0.0/18",
+ "MapPublicIpOnLaunch": true,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcPublicSubnet2DefaultRoute97F91067": Object {
+ "DependsOn": Array [
+ "VpcVPCGWBF912B6E",
+ ],
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPublicSubnet2EIP3C605A87": Object {
+ "Properties": Object {
+ "Domain": "vpc",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::EIP",
+ },
+ "VpcPublicSubnet2NATGateway9182C01D": Object {
+ "Properties": Object {
+ "AllocationId": Object {
+ "Fn::GetAtt": Array [
+ "VpcPublicSubnet2EIP3C605A87",
+ "AllocationId",
+ ],
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::NatGateway",
+ },
+ "VpcPublicSubnet2RouteTable94F7E489": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPublicSubnet2RouteTableAssociationDD5762D8": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPublicSubnet2Subnet691E08A3": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.64.0/18",
+ "MapPublicIpOnLaunch": true,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcVPCGWBF912B6E": Object {
+ "Properties": Object {
+ "InternetGatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::VPCGatewayAttachment",
+ },
+ },
+}
+`;
+
+exports[`Test deployment with existing Sagemaker Notebook instance 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "SagemakerNotebook": Object {
+ "Properties": Object {
+ "DirectInternetAccess": "Disabled",
+ "InstanceType": "ml.t2.medium",
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
+ },
+ "RoleArn": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "SecurityGroupsecuritygroup00653C55",
+ "GroupId",
+ ],
+ },
+ ],
+ "SubnetId": Object {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A",
+ },
+ },
+ "Type": "AWS::SageMaker::NotebookInstance",
+ },
+ "SagemakerRole5FDB64E1": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": Array [
+ Object {
+ "Ref": "SagemakerRole5FDB64E1",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "SecurityGroupsecuritygroup00653C55": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/SecurityGroup-security-group",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "from 0.0.0.0/0:443",
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "Vpc8378EB38": Object {
+ "Properties": Object {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::VPC",
+ },
+ "VpcFlowLog8FF33A73": Object {
+ "Properties": Object {
+ "DeliverLogsPermissionArn": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": Object {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9",
+ },
+ "ResourceId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ "ResourceType": "VPC",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ "TrafficType": "ALL",
+ },
+ "Type": "AWS::EC2::FlowLog",
+ },
+ "VpcFlowLogIAMRole6A475D41": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "vpc-flow-logs.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": Object {
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": Array [
+ Object {
+ "Ref": "VpcFlowLogIAMRole6A475D41",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "VpcFlowLogLogGroup7B5C56B9": Object {
+ "DeletionPolicy": "Retain",
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "RetentionInDays": 731,
+ },
+ "Type": "AWS::Logs::LogGroup",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "VpcIGWD7BA715C": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::InternetGateway",
+ },
+ "VpcPrivateSubnet1DefaultRouteBE02A9ED": Object {
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": Object {
+ "Ref": "VpcPublicSubnet1NATGateway4D7517AA",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPrivateSubnet1RouteTableAssociation70C59FA6": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet1RouteTableB2C5B500",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPrivateSubnet1Subnet536B997A",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPrivateSubnet1RouteTableB2C5B500": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPrivateSubnet1Subnet536B997A": Object {
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.128.0/18",
+ "MapPublicIpOnLaunch": false,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcPrivateSubnet2DefaultRoute060D2087": Object {
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "NatGatewayId": Object {
+ "Ref": "VpcPublicSubnet2NATGateway9182C01D",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPrivateSubnet2RouteTableA678073B": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPrivateSubnet2RouteTableAssociationA89CAD56": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPrivateSubnet2RouteTableA678073B",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPrivateSubnet2Subnet3788AAA1",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPrivateSubnet2Subnet3788AAA1": Object {
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.192.0/18",
+ "MapPublicIpOnLaunch": false,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Private",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PrivateSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcPublicSubnet1DefaultRoute3DA9E72A": Object {
+ "DependsOn": Array [
+ "VpcVPCGWBF912B6E",
+ ],
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPublicSubnet1EIPD7E02669": Object {
+ "Properties": Object {
+ "Domain": "vpc",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::EIP",
+ },
+ "VpcPublicSubnet1NATGateway4D7517AA": Object {
+ "Properties": Object {
+ "AllocationId": Object {
+ "Fn::GetAtt": Array [
+ "VpcPublicSubnet1EIPD7E02669",
+ "AllocationId",
+ ],
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet1Subnet5C2D37C4",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::NatGateway",
+ },
+ "VpcPublicSubnet1RouteTable6C95E38E": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet1",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPublicSubnet1RouteTableAssociation97140677": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet1RouteTable6C95E38E",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet1Subnet5C2D37C4",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPublicSubnet1Subnet5C2D37C4": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.0.0/18",
+ "MapPublicIpOnLaunch": true,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "Name",
"Value": "Default/Vpc/PublicSubnet1",
},
],
- "VpcId": Object {
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcPublicSubnet2DefaultRoute97F91067": Object {
+ "DependsOn": Array [
+ "VpcVPCGWBF912B6E",
+ ],
+ "Properties": Object {
+ "DestinationCidrBlock": "0.0.0.0/0",
+ "GatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489",
+ },
+ },
+ "Type": "AWS::EC2::Route",
+ },
+ "VpcPublicSubnet2EIP3C605A87": Object {
+ "Properties": Object {
+ "Domain": "vpc",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::EIP",
+ },
+ "VpcPublicSubnet2NATGateway9182C01D": Object {
+ "Properties": Object {
+ "AllocationId": Object {
+ "Fn::GetAtt": Array [
+ "VpcPublicSubnet2EIP3C605A87",
+ "AllocationId",
+ ],
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::NatGateway",
+ },
+ "VpcPublicSubnet2RouteTable94F7E489": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPublicSubnet2RouteTableAssociationDD5762D8": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPublicSubnet2Subnet691E08A3": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.64.0/18",
+ "MapPublicIpOnLaunch": true,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcVPCGWBF912B6E": Object {
+ "Properties": Object {
+ "InternetGatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::VPCGatewayAttachment",
+ },
+ },
+}
+`;
+
+exports[`Test minimal deployment of Sagemaker Inference Endpoint with VPC 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/ReplaceEndpointDefaultSecurityGroup-security-group",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "SecurityGroupIngress": Array [
+ Object {
+ "CidrIp": Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ "Description": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "from ",
+ Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ ":443",
+ ],
+ ],
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "ReplaceModelDefaultSecurityGroup38936A39": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W5",
+ "reason": "Egress of 0.0.0.0/0 is default and generally considered OK",
+ },
+ Object {
+ "id": "W40",
+ "reason": "Egress IPProtocol of -1 is default and generally considered OK",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "GroupDescription": "Default/ReplaceModelDefaultSecurityGroup",
+ "SecurityGroupEgress": Array [
+ Object {
+ "CidrIp": "0.0.0.0/0",
+ "Description": "Allow all outbound traffic by default",
+ "IpProtocol": "-1",
+ },
+ ],
+ "SecurityGroupIngress": Array [
+ Object {
+ "CidrIp": Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ "Description": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "from ",
+ Object {
+ "Fn::GetAtt": Array [
+ "Vpc8378EB38",
+ "CidrBlock",
+ ],
+ },
+ ":443",
+ ],
+ ],
+ },
+ "FromPort": 443,
+ "IpProtocol": "tcp",
+ "ToPort": 443,
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::SecurityGroup",
+ },
+ "SagemakerEndpoint": Object {
+ "DependsOn": Array [
+ "SagemakerEndpointConfig",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerEndpointConfig",
+ "EndpointConfigName",
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Endpoint",
+ },
+ "SagemakerEndpointConfig": Object {
+ "DependsOn": Array [
+ "SagemakerModel",
+ ],
+ "Properties": Object {
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
+ },
+ "ProductionVariants": Array [
+ Object {
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerModel",
+ "ModelName",
+ ],
+ },
+ "VariantName": "AllTraffic",
+ },
+ ],
+ },
+ "Type": "AWS::SageMaker::EndpointConfig",
+ },
+ "SagemakerModel": Object {
+ "DependsOn": Array [
+ "SagemakerRoleDefaultPolicy9DD21C3C",
+ "SagemakerRole5FDB64E1",
+ ],
+ "Properties": Object {
+ "ExecutionRoleArn": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
+ },
+ "VpcConfig": Object {
+ "SecurityGroupIds": Array [
+ Object {
+ "Fn::GetAtt": Array [
+ "ReplaceModelDefaultSecurityGroup38936A39",
+ "GroupId",
+ ],
+ },
+ ],
+ "Subnets": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
+ },
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Model",
+ },
+ "SagemakerRole5FDB64E1": Object {
+ "Properties": Object {
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ec2:CreateNetworkInterface",
+ "ec2:CreateNetworkInterfacePermission",
+ "ec2:DeleteNetworkInterface",
+ "ec2:DeleteNetworkInterfacePermission",
+ "ec2:DescribeNetworkInterfaces",
+ "ec2:AssignPrivateIpAddresses",
+ "ec2:UnassignPrivateIpAddresses",
+ "ec2:DescribeVpcs",
+ "ec2:DescribeDhcpOptions",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeSecurityGroups",
+ ],
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": Array [
+ Object {
+ "Ref": "SagemakerRole5FDB64E1",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Policy",
+ },
+ "Vpc8378EB38": Object {
+ "Properties": Object {
+ "CidrBlock": "10.0.0.0/16",
+ "EnableDnsHostnames": true,
+ "EnableDnsSupport": true,
+ "InstanceTenancy": "default",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::VPC",
+ },
+ "VpcFlowLog8FF33A73": Object {
+ "Properties": Object {
+ "DeliverLogsPermissionArn": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ "LogDestinationType": "cloud-watch-logs",
+ "LogGroupName": Object {
+ "Ref": "VpcFlowLogLogGroup7B5C56B9",
+ },
+ "ResourceId": Object {
"Ref": "Vpc8378EB38",
},
+ "ResourceType": "VPC",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ "TrafficType": "ALL",
},
- "Type": "AWS::EC2::RouteTable",
+ "Type": "AWS::EC2::FlowLog",
},
- "VpcPublicSubnet1RouteTableAssociation97140677": Object {
+ "VpcFlowLogIAMRole6A475D41": Object {
"Properties": Object {
- "RouteTableId": Object {
- "Ref": "VpcPublicSubnet1RouteTable6C95E38E",
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "vpc-flow-logs.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
},
- "SubnetId": Object {
- "Ref": "VpcPublicSubnet1Subnet5C2D37C4",
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc",
+ },
+ ],
+ },
+ "Type": "AWS::IAM::Role",
+ },
+ "VpcFlowLogIAMRoleDefaultPolicy406FB995": Object {
+ "Properties": Object {
+ "PolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ "logs:DescribeLogStreams",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogLogGroup7B5C56B9",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "VpcFlowLogIAMRole6A475D41",
+ "Arn",
+ ],
+ },
+ },
+ ],
+ "Version": "2012-10-17",
},
+ "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995",
+ "Roles": Array [
+ Object {
+ "Ref": "VpcFlowLogIAMRole6A475D41",
+ },
+ ],
},
- "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Type": "AWS::IAM::Policy",
},
- "VpcPublicSubnet1Subnet5C2D37C4": Object {
+ "VpcFlowLogLogGroup7B5C56B9": Object {
+ "DeletionPolicy": "Retain",
"Metadata": Object {
"cfn_nag": Object {
"rules_to_suppress": Array [
Object {
- "id": "W33",
- "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ "id": "W84",
+ "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)",
},
],
},
},
"Properties": Object {
- "AvailabilityZone": Object {
- "Fn::Select": Array [
- 0,
- Object {
- "Fn::GetAZs": "",
- },
+ "RetentionInDays": 731,
+ },
+ "Type": "AWS::Logs::LogGroup",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "VpcS3A5408339": Object {
+ "Properties": Object {
+ "RouteTableIds": Array [
+ Object {
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B",
+ },
+ Object {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764",
+ },
+ ],
+ "ServiceName": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "com.amazonaws.",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".s3",
+ ],
],
},
- "CidrBlock": "10.0.0.0/18",
- "MapPublicIpOnLaunch": true,
- "Tags": Array [
+ "VpcEndpointType": "Gateway",
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::VPCEndpoint",
+ },
+ "VpcSAGEMAKERRUNTIME337E125A": Object {
+ "Properties": Object {
+ "PrivateDnsEnabled": true,
+ "SecurityGroupIds": Array [
Object {
- "Key": "aws-cdk:subnet-name",
- "Value": "Public",
+ "Fn::GetAtt": Array [
+ "ReplaceEndpointDefaultSecurityGroupsecuritygroupB97DD1AF",
+ "GroupId",
+ ],
},
+ ],
+ "ServiceName": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "com.amazonaws.",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".sagemaker.runtime",
+ ],
+ ],
+ },
+ "SubnetIds": Array [
Object {
- "Key": "aws-cdk:subnet-type",
- "Value": "Public",
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
},
Object {
- "Key": "Name",
- "Value": "Default/Vpc/PublicSubnet1",
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
},
],
+ "VpcEndpointType": "Interface",
"VpcId": Object {
"Ref": "Vpc8378EB38",
},
},
- "Type": "AWS::EC2::Subnet",
+ "Type": "AWS::EC2::VPCEndpoint",
},
- "VpcPublicSubnet2DefaultRoute97F91067": Object {
- "DependsOn": Array [
- "VpcVPCGWBF912B6E",
- ],
+ "VpcisolatedSubnet1RouteTableAssociationD259E31A": Object {
"Properties": Object {
- "DestinationCidrBlock": "0.0.0.0/0",
- "GatewayId": Object {
- "Ref": "VpcIGWD7BA715C",
- },
"RouteTableId": Object {
- "Ref": "VpcPublicSubnet2RouteTable94F7E489",
+ "Ref": "VpcisolatedSubnet1RouteTableE442650B",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcisolatedSubnet1SubnetE62B1B9B",
},
},
- "Type": "AWS::EC2::Route",
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
},
- "VpcPublicSubnet2EIP3C605A87": Object {
+ "VpcisolatedSubnet1RouteTableE442650B": Object {
"Properties": Object {
- "Domain": "vpc",
"Tags": Array [
Object {
"Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
+ "Value": "Default/Vpc/isolatedSubnet1",
},
],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
},
- "Type": "AWS::EC2::EIP",
+ "Type": "AWS::EC2::RouteTable",
},
- "VpcPublicSubnet2NATGateway9182C01D": Object {
+ "VpcisolatedSubnet1SubnetE62B1B9B": Object {
"Properties": Object {
- "AllocationId": Object {
- "Fn::GetAtt": Array [
- "VpcPublicSubnet2EIP3C605A87",
- "AllocationId",
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 0,
+ Object {
+ "Fn::GetAZs": "",
+ },
],
},
- "SubnetId": Object {
- "Ref": "VpcPublicSubnet2Subnet691E08A3",
- },
+ "CidrBlock": "10.0.0.0/18",
+ "MapPublicIpOnLaunch": false,
"Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "isolated",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Isolated",
+ },
Object {
"Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
+ "Value": "Default/Vpc/isolatedSubnet1",
},
],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
},
- "Type": "AWS::EC2::NatGateway",
+ "Type": "AWS::EC2::Subnet",
},
- "VpcPublicSubnet2RouteTable94F7E489": Object {
+ "VpcisolatedSubnet2RouteTable334F9764": Object {
"Properties": Object {
"Tags": Array [
Object {
"Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
+ "Value": "Default/Vpc/isolatedSubnet2",
},
],
"VpcId": Object {
"Ref": "Vpc8378EB38",
},
- },
- "Type": "AWS::EC2::RouteTable",
- },
- "VpcPublicSubnet2RouteTableAssociationDD5762D8": Object {
- "Properties": Object {
- "RouteTableId": Object {
- "Ref": "VpcPublicSubnet2RouteTable94F7E489",
- },
- "SubnetId": Object {
- "Ref": "VpcPublicSubnet2Subnet691E08A3",
- },
- },
- "Type": "AWS::EC2::SubnetRouteTableAssociation",
- },
- "VpcPublicSubnet2Subnet691E08A3": Object {
- "Metadata": Object {
- "cfn_nag": Object {
- "rules_to_suppress": Array [
- Object {
- "id": "W33",
- "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
- },
- ],
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcisolatedSubnet2RouteTableAssociation25A4716F": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcisolatedSubnet2RouteTable334F9764",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcisolatedSubnet2Subnet39217055",
},
},
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcisolatedSubnet2Subnet39217055": Object {
"Properties": Object {
"AvailabilityZone": Object {
"Fn::Select": Array [
@@ -916,19 +4125,19 @@ Object {
],
},
"CidrBlock": "10.0.64.0/18",
- "MapPublicIpOnLaunch": true,
+ "MapPublicIpOnLaunch": false,
"Tags": Array [
Object {
"Key": "aws-cdk:subnet-name",
- "Value": "Public",
+ "Value": "isolated",
},
Object {
"Key": "aws-cdk:subnet-type",
- "Value": "Public",
+ "Value": "Isolated",
},
Object {
"Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
+ "Value": "Default/Vpc/isolatedSubnet2",
},
],
"VpcId": Object {
@@ -937,17 +4146,6 @@ Object {
},
"Type": "AWS::EC2::Subnet",
},
- "VpcVPCGWBF912B6E": Object {
- "Properties": Object {
- "InternetGatewayId": Object {
- "Ref": "VpcIGWD7BA715C",
- },
- "VpcId": Object {
- "Ref": "Vpc8378EB38",
- },
- },
- "Type": "AWS::EC2::VPCGatewayAttachment",
- },
},
}
`;
@@ -1052,6 +4250,20 @@ Object {
"Type": "AWS::IAM::Role",
},
"SagemakerRoleDefaultPolicy9DD21C3C": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
+ },
+ ],
+ },
+ },
"Properties": Object {
"PolicyDocument": Object {
"Statement": Array [
@@ -1122,6 +4334,103 @@ Object {
],
},
},
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
Object {
"Action": "iam:GetRole",
"Effect": "Allow",
@@ -1611,95 +4920,448 @@ Object {
"AllocationId",
],
},
- "SubnetId": Object {
- "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ },
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ },
+ "Type": "AWS::EC2::NatGateway",
+ },
+ "VpcPublicSubnet2RouteTable94F7E489": Object {
+ "Properties": Object {
+ "Tags": Array [
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::RouteTable",
+ },
+ "VpcPublicSubnet2RouteTableAssociationDD5762D8": Object {
+ "Properties": Object {
+ "RouteTableId": Object {
+ "Ref": "VpcPublicSubnet2RouteTable94F7E489",
+ },
+ "SubnetId": Object {
+ "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ },
+ },
+ "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ },
+ "VpcPublicSubnet2Subnet691E08A3": Object {
+ "Metadata": Object {
+ "cfn_nag": Object {
+ "rules_to_suppress": Array [
+ Object {
+ "id": "W33",
+ "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ },
+ ],
+ },
+ },
+ "Properties": Object {
+ "AvailabilityZone": Object {
+ "Fn::Select": Array [
+ 1,
+ Object {
+ "Fn::GetAZs": "",
+ },
+ ],
+ },
+ "CidrBlock": "10.0.64.0/18",
+ "MapPublicIpOnLaunch": true,
+ "Tags": Array [
+ Object {
+ "Key": "aws-cdk:subnet-name",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "aws-cdk:subnet-type",
+ "Value": "Public",
+ },
+ Object {
+ "Key": "Name",
+ "Value": "Default/Vpc/PublicSubnet2",
+ },
+ ],
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::Subnet",
+ },
+ "VpcVPCGWBF912B6E": Object {
+ "Properties": Object {
+ "InternetGatewayId": Object {
+ "Ref": "VpcIGWD7BA715C",
+ },
+ "VpcId": Object {
+ "Ref": "Vpc8378EB38",
+ },
+ },
+ "Type": "AWS::EC2::VPCGatewayAttachment",
+ },
+ },
+}
+`;
+
+exports[`Test minimal deployment with no properties using internal IAM role 1`] = `
+Object {
+ "Resources": Object {
+ "EncryptionKey1B843E66": Object {
+ "DeletionPolicy": "Retain",
+ "Properties": Object {
+ "EnableKeyRotation": true,
+ "KeyPolicy": Object {
+ "Statement": Array [
+ Object {
+ "Action": Array [
+ "kms:Create*",
+ "kms:Describe*",
+ "kms:Enable*",
+ "kms:List*",
+ "kms:Put*",
+ "kms:Update*",
+ "kms:Revoke*",
+ "kms:Disable*",
+ "kms:Get*",
+ "kms:Delete*",
+ "kms:ScheduleKeyDeletion",
+ "kms:CancelKeyDeletion",
+ "kms:GenerateDataKey",
+ "kms:TagResource",
+ "kms:UntagResource",
+ ],
+ "Effect": "Allow",
+ "Principal": Object {
+ "AWS": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":iam::",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":root",
+ ],
+ ],
+ },
+ },
+ "Resource": "*",
+ },
+ ],
+ "Version": "2012-10-17",
+ },
+ },
+ "Type": "AWS::KMS::Key",
+ "UpdateReplacePolicy": "Retain",
+ },
+ "SagemakerEndpoint": Object {
+ "DependsOn": Array [
+ "SagemakerEndpointConfig",
+ ],
+ "Properties": Object {
+ "EndpointConfigName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerEndpointConfig",
+ "EndpointConfigName",
+ ],
+ },
+ },
+ "Type": "AWS::SageMaker::Endpoint",
+ },
+ "SagemakerEndpointConfig": Object {
+ "DependsOn": Array [
+ "SagemakerModel",
+ ],
+ "Properties": Object {
+ "KmsKeyId": Object {
+ "Ref": "EncryptionKey1B843E66",
},
- "Tags": Array [
+ "ProductionVariants": Array [
Object {
- "Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
+ "InitialInstanceCount": 1,
+ "InitialVariantWeight": 1,
+ "InstanceType": "ml.m4.xlarge",
+ "ModelName": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerModel",
+ "ModelName",
+ ],
+ },
+ "VariantName": "AllTraffic",
},
],
},
- "Type": "AWS::EC2::NatGateway",
+ "Type": "AWS::SageMaker::EndpointConfig",
},
- "VpcPublicSubnet2RouteTable94F7E489": Object {
+ "SagemakerModel": Object {
+ "DependsOn": Array [
+ "SagemakerRoleDefaultPolicy9DD21C3C",
+ "SagemakerRole5FDB64E1",
+ ],
"Properties": Object {
- "Tags": Array [
- Object {
- "Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
- },
- ],
- "VpcId": Object {
- "Ref": "Vpc8378EB38",
+ "ExecutionRoleArn": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ "PrimaryContainer": Object {
+ "Image": ".dkr.ecr..amazonaws.com/linear-learner:latest",
+ "ModelDataUrl": "s3:////model.tar.gz",
},
},
- "Type": "AWS::EC2::RouteTable",
+ "Type": "AWS::SageMaker::Model",
},
- "VpcPublicSubnet2RouteTableAssociationDD5762D8": Object {
+ "SagemakerRole5FDB64E1": Object {
"Properties": Object {
- "RouteTableId": Object {
- "Ref": "VpcPublicSubnet2RouteTable94F7E489",
- },
- "SubnetId": Object {
- "Ref": "VpcPublicSubnet2Subnet691E08A3",
+ "AssumeRolePolicyDocument": Object {
+ "Statement": Array [
+ Object {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": Object {
+ "Service": "sagemaker.amazonaws.com",
+ },
+ },
+ ],
+ "Version": "2012-10-17",
},
},
- "Type": "AWS::EC2::SubnetRouteTableAssociation",
+ "Type": "AWS::IAM::Role",
},
- "VpcPublicSubnet2Subnet691E08A3": Object {
+ "SagemakerRoleDefaultPolicy9DD21C3C": Object {
"Metadata": Object {
"cfn_nag": Object {
"rules_to_suppress": Array [
Object {
- "id": "W33",
- "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true",
+ "id": "W12",
+ "reason": "Sagemaker needs the following minimum required permissions to access ENIs in a VPC, ECR for custom model images, and elastic inference.",
+ },
+ Object {
+ "id": "W76",
+ "reason": "Complex role becuase Sagemaker needs permissions to access several services",
},
],
},
},
"Properties": Object {
- "AvailabilityZone": Object {
- "Fn::Select": Array [
- 1,
+ "PolicyDocument": Object {
+ "Statement": Array [
Object {
- "Fn::GetAZs": "",
+ "Action": Array [
+ "sagemaker:CreateTrainingJob",
+ "sagemaker:DescribeTrainingJob",
+ "sagemaker:CreateModel",
+ "sagemaker:DescribeModel",
+ "sagemaker:DeleteModel",
+ "sagemaker:CreateEndpoint",
+ "sagemaker:CreateEndpointConfig",
+ "sagemaker:DescribeEndpoint",
+ "sagemaker:DescribeEndpointConfig",
+ "sagemaker:DeleteEndpoint",
+ "sagemaker:DeleteEndpointConfig",
+ "sagemaker:InvokeEndpoint",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":sagemaker:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:GetLogEvents",
+ "logs:PutLogEvents",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":logs:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":log-group:/aws/sagemaker/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": Array [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:DescribeRepositories",
+ "ecr:DescribeImages",
+ "ecr:BatchGetImage",
+ ],
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":ecr:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":repository/*",
+ ],
+ ],
+ },
+ },
+ Object {
+ "Action": "ecr:GetAuthorizationToken",
+ "Effect": "Allow",
+ "Resource": "*",
+ },
+ Object {
+ "Action": Array [
+ "kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey",
+ ],
+ "Effect": "Allow",
+ "Resource": Array [
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":key/*",
+ ],
+ ],
+ },
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":kms:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":alias/*",
+ ],
+ ],
+ },
+ ],
+ },
+ Object {
+ "Action": Array [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListBucket",
+ ],
+ "Effect": "Allow",
+ "Resource": "arn:aws:s3:::*",
+ },
+ Object {
+ "Action": "iam:GetRole",
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
+ },
+ Object {
+ "Action": "iam:PassRole",
+ "Condition": Object {
+ "StringLike": Object {
+ "iam:PassedToService": "sagemaker.amazonaws.com",
+ },
+ },
+ "Effect": "Allow",
+ "Resource": Object {
+ "Fn::GetAtt": Array [
+ "SagemakerRole5FDB64E1",
+ "Arn",
+ ],
+ },
},
],
+ "Version": "2012-10-17",
},
- "CidrBlock": "10.0.64.0/18",
- "MapPublicIpOnLaunch": true,
- "Tags": Array [
- Object {
- "Key": "aws-cdk:subnet-name",
- "Value": "Public",
- },
- Object {
- "Key": "aws-cdk:subnet-type",
- "Value": "Public",
- },
+ "PolicyName": "SagemakerRoleDefaultPolicy9DD21C3C",
+ "Roles": Array [
Object {
- "Key": "Name",
- "Value": "Default/Vpc/PublicSubnet2",
+ "Ref": "SagemakerRole5FDB64E1",
},
],
- "VpcId": Object {
- "Ref": "Vpc8378EB38",
- },
- },
- "Type": "AWS::EC2::Subnet",
- },
- "VpcVPCGWBF912B6E": Object {
- "Properties": Object {
- "InternetGatewayId": Object {
- "Ref": "VpcIGWD7BA715C",
- },
- "VpcId": Object {
- "Ref": "Vpc8378EB38",
- },
},
- "Type": "AWS::EC2::VPCGatewayAttachment",
+ "Type": "AWS::IAM::Policy",
},
},
}
diff --git a/source/patterns/@aws-solutions-constructs/core/test/sagemaker-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/sagemaker-helper.test.ts
index 6c2ed76cf..524efb383 100644
--- a/source/patterns/@aws-solutions-constructs/core/test/sagemaker-helper.test.ts
+++ b/source/patterns/@aws-solutions-constructs/core/test/sagemaker-helper.test.ts
@@ -11,10 +11,10 @@
* and limitations under the License.
*/
-import { Stack } from "@aws-cdk/core";
-import * as iam from "@aws-cdk/aws-iam";
-import * as kms from "@aws-cdk/aws-kms";
-import * as ec2 from "@aws-cdk/aws-ec2";
+import { Stack } from '@aws-cdk/core';
+import * as iam from '@aws-cdk/aws-iam';
+import * as kms from '@aws-cdk/aws-kms';
+import * as ec2 from '@aws-cdk/aws-ec2';
import * as defaults from '../';
import { SynthUtils } from '@aws-cdk/assert';
import '@aws-cdk/assert/jest';
@@ -25,10 +25,10 @@ import '@aws-cdk/assert/jest';
test('Test minimal deployment with no properties', () => {
// Stack
const stack = new Stack();
- const sagemakerRole = new iam.Role(stack, "SagemakerRole", {
- assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
});
- // Build SageMaker Notebook Instance
+ // Build Sagemaker Notebook Instance
defaults.buildSagemakerNotebook(stack, {
role: sagemakerRole,
});
@@ -42,14 +42,14 @@ test('Test minimal deployment with no properties', () => {
test('Test deployment with VPC', () => {
// Stack
const stack = new Stack();
- const sagemakerRole = new iam.Role(stack, "SagemakerRole", {
- assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
});
let sagemaker;
let vpc;
let sg;
- // Build SageMaker Notebook Instance
+ // Build Sagemaker Notebook Instance
[sagemaker, vpc, sg] = defaults.buildSagemakerNotebook(stack, {
role: sagemakerRole,
});
@@ -67,17 +67,16 @@ test('Test deployment with VPC', () => {
test('Test deployment w/o VPC', () => {
// Stack
const stack = new Stack();
- const sagemakerRole = new iam.Role(stack, "SagemakerRole", {
- assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
});
- // Build SageMaker Notebook Instance
+ // Build Sagemaker Notebook Instance
defaults.buildSagemakerNotebook(stack, {
role: sagemakerRole,
- deployInsideVpc: false
+ deployInsideVpc: false,
});
// Assertion
expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
-
});
// --------------------------------------------------------------
@@ -86,26 +85,23 @@ test('Test deployment w/o VPC', () => {
test('Test deployment w/ existing VPC', () => {
// Stack
const stack = new Stack();
- const sagemakerRole = new iam.Role(stack, "SagemakerRole", {
- assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
});
- // Build SageMaker Notebook Instance
+ // Build Sagemaker Notebook Instance
defaults.buildSagemakerNotebook(stack, {
role: sagemakerRole,
deployInsideVpc: true,
sagemakerNotebookProps: {
subnetId: 'subnet-deadbeef',
- securityGroupIds: ['sg-deadbeef']
- }
+ securityGroupIds: ['sg-deadbeef'],
+ },
});
- expect(stack).toHaveResource("AWS::SageMaker::NotebookInstance", {
- DirectInternetAccess: "Disabled",
- SecurityGroupIds: [
- "sg-deadbeef"
- ],
- SubnetId: "subnet-deadbeef"
+ expect(stack).toHaveResource('AWS::SageMaker::NotebookInstance', {
+ DirectInternetAccess: 'Disabled',
+ SecurityGroupIds: ['sg-deadbeef'],
+ SubnetId: 'subnet-deadbeef',
});
-
});
// --------------------------------------------------------------
@@ -114,28 +110,48 @@ test('Test deployment w/ existing VPC', () => {
test('Test deployment w/ override', () => {
// Stack
const stack = new Stack();
- const sagemakerRole = new iam.Role(stack, "SagemakerRole", {
- assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
});
const key = new kms.Key(stack, 'MyEncryptionKey');
- // Build SageMaker Notebook Instance
+ // Build Sagemaker Notebook Instance
defaults.buildSagemakerNotebook(stack, {
role: sagemakerRole,
sagemakerNotebookProps: {
instanceType: 'ml.c4.2xlarge',
- kmsKeyId: key.keyArn
- }
+ kmsKeyId: key.keyArn,
+ },
});
- expect(stack).toHaveResource("AWS::SageMaker::NotebookInstance", {
- DirectInternetAccess: "Disabled",
- InstanceType: "ml.c4.2xlarge",
+ expect(stack).toHaveResource('AWS::SageMaker::NotebookInstance', {
+ DirectInternetAccess: 'Disabled',
+ InstanceType: 'ml.c4.2xlarge',
KmsKeyId: {
- "Fn::GetAtt": [
- "MyEncryptionKeyD795679F",
- "Arn"
- ]
- }
+ 'Fn::GetAtt': ['MyEncryptionKeyD795679F', 'Arn'],
+ },
+ });
+});
+
+// ----------------------------------------------------------
+// Test deployment with existing Sagemaker Notebook instance
+// ----------------------------------------------------------
+test('Test deployment with existing Sagemaker Notebook instance', () => {
+ // Stack
+ const stack = new Stack();
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
+ });
+ // Build Sagemaker Notebook Instance
+ const [noteBookInstance] = defaults.buildSagemakerNotebook(stack, {
+ role: sagemakerRole,
+ });
+
+ // Build Sagemaker Notebook Instance
+ defaults.buildSagemakerNotebook(stack, {
+ existingNotebookObj: noteBookInstance,
+ role: sagemakerRole,
});
+ // Assertion
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
});
// --------------------------------------------------------------
@@ -144,18 +160,284 @@ test('Test deployment w/ override', () => {
test('Test exception', () => {
// Stack
const stack = new Stack();
- const sagemakerRole = new iam.Role(stack, "SagemakerRole", {
- assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
});
expect(() => {
- // Build SageMaker Notebook Instance
+ // Build Sagemaker Notebook Instance
defaults.buildSagemakerNotebook(stack, {
role: sagemakerRole,
deployInsideVpc: true,
sagemakerNotebookProps: {
subnetId: 'subnet-deadbeef',
- }
+ },
});
}).toThrowError();
});
+
+// --------------------------------------------------------------------------------------
+// Test minimal deployment of Sagemaker Inference Endpoint no VPC using internal IAM role
+// --------------------------------------------------------------------------------------
+test('Test minimal deployment with no properties using internal IAM role', () => {
+ // Stack
+ const stack = new Stack();
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ });
+ // Assertion
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+});
+
+// ----------------------------------------------------------------
+// Test minimal deployment of Sagemaker Inference Endpoint with VPC
+// ----------------------------------------------------------------
+test('Test minimal deployment of Sagemaker Inference Endpoint with VPC', () => {
+ // Stack
+ const stack = new Stack();
+
+ // create a VPC with required VPC S3 gateway and SAGEMAKER_RUNTIME Interface
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ // Add S3 VPC Gateway Endpint, required by Sagemaker to access Models artifacts via AWS private network
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.S3);
+ // Add SAGEMAKER_RUNTIME VPC Interface Endpint, required by the lambda function to invoke the SageMaker endpoint
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ vpc,
+ });
+ // Assertion
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+});
+
+// -------------------------------------------------------------------------
+// Test deployment of Sagemaker Inference Endpoint with properties overwrite
+// -------------------------------------------------------------------------
+test('Test deployment of Sagemaker Inference Endpoint with properties overwrite', () => {
+ // Stack
+ const stack = new Stack();
+
+ // create a VPC with required VPC S3 gateway and SAGEMAKER_RUNTIME Interface
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ // Add S3 VPC Gateway Endpint, required by Sagemaker to access Models artifacts via AWS private network
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.S3);
+ // Add SAGEMAKER_RUNTIME VPC Interface Endpint, required by the lambda function to invoke the SageMaker endpoint
+ defaults.AddAwsServiceEndpoint(stack, vpc, defaults.ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+ // create encryption key
+ const encryptionkey = new kms.Key(stack, 'MyEndpointConfigEncryptionKey');
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, {
+ modelProps: {
+ modelName: 'linear-learner-model',
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ endpointConfigProps: {
+ endpointConfigName: 'linear-learner-endpoint-config',
+ productionVariants: [
+ {
+ modelName: 'linear-learner-model',
+ initialInstanceCount: 1,
+ initialVariantWeight: 1.0,
+ instanceType: 'ml.m4.large',
+ variantName: 'AllTraffic',
+ acceleratorType: 'ml.eia2.medium',
+ },
+ ],
+ kmsKeyId: encryptionkey.keyArn,
+ },
+ endpointProps: {
+ endpointConfigName: 'linear-learner-endpoint-config',
+ endpointName: 'linear-learner-endpoint',
+ },
+ vpc,
+ });
+ // Assertion
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+});
+
+// --------------------------------------------------------------
+// Test deployment of existing Sagemaker Endpoint
+// --------------------------------------------------------------
+test('Test deployment of existing Sagemaker Endpoint', () => {
+ // Stack
+ const stack = new Stack();
+
+ const [sagemakerEndpoint] = defaults.deploySagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ });
+
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, {
+ existingSagemakerEndpointObj: sagemakerEndpoint,
+ });
+ // Assertion
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+});
+
+// ------------------------------------------------------------------------
+// Test deployment of sagemaker endpoint with a customer provided role
+// ------------------------------------------------------------------------
+test('Test deployment of sagemaker endpoint with a customer provided role', () => {
+ // Stack
+ const stack = new Stack();
+ // Create IAM Role to be assumed by Sagemaker
+ const sagemakerRole = new iam.Role(stack, 'SagemakerRole', {
+ assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'),
+ });
+ sagemakerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSageMakerFullAccess'));
+
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, {
+ modelProps: {
+ executionRoleArn: sagemakerRole.roleArn,
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ });
+
+ // Assertion 1
+ expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+});
+
+// ---------------------------------------------------------------
+// Test exception for not providing primaryContainer in modelProps
+// ---------------------------------------------------------------
+test('Test exception for not providing primaryContainer in modelProps', () => {
+ // Stack
+ const stack = new Stack();
+
+ const app = () => {
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, {
+ modelProps: {},
+ });
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// -------------------------------------------------------------------------
+// Test exception for not providing modelProps
+// -------------------------------------------------------------------------
+test('Test exception for not providing modelProps', () => {
+ // Stack
+ const stack = new Stack();
+
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ const app = () => {
+ // Build Sagemaker Inference Endpoint
+ defaults.deploySagemakerEndpoint(stack, { vpc });
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// -------------------------------------------------------------------------
+// Test exception for not providing modelProps or existingSagemkaerObj
+// -------------------------------------------------------------------------
+test('Test exception for not providing modelProps or existingSagemkaerObj', () => {
+ // Stack
+ const stack = new Stack();
+
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ const app = () => {
+ // Build Sagemaker Inference Endpoint
+ defaults.BuildSagemakerEndpoint(stack, { vpc });
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
+
+// -----------------------------------------------------------------------------------------
+// Test exception for not providing private or isolated subnets in an existing vpc
+// -----------------------------------------------------------------------------------------
+test('Test exception for not providing private or isolated subnets in an existing vpc', () => {
+ // Stack
+ const stack = new Stack();
+
+ // create a VPC
+ const vpc = defaults.buildVpc(stack, {
+ defaultVpcProps: defaults.DefaultIsolatedVpcProps(),
+ userVpcProps: {
+ natGateways: 0,
+ subnetConfiguration: [
+ {
+ cidrMask: 18,
+ name: 'public',
+ subnetType: ec2.SubnetType.PUBLIC,
+ },
+ ],
+ },
+ constructVpcProps: {
+ enableDnsHostnames: true,
+ enableDnsSupport: true,
+ },
+ });
+
+ const app = () => {
+ // Build Sagemaker Inference Endpoint
+ defaults.deploySagemakerEndpoint(stack, {
+ modelProps: {
+ primaryContainer: {
+ image: '.dkr.ecr..amazonaws.com/linear-learner:latest',
+ modelDataUrl: 's3:////model.tar.gz',
+ },
+ },
+ vpc,
+ });
+ };
+ // Assertion 1
+ expect(app).toThrowError();
+});
diff --git a/source/patterns/@aws-solutions-constructs/core/test/vpc-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/vpc-helper.test.ts
index cda3b5be9..ae81db621 100644
--- a/source/patterns/@aws-solutions-constructs/core/test/vpc-helper.test.ts
+++ b/source/patterns/@aws-solutions-constructs/core/test/vpc-helper.test.ts
@@ -11,13 +11,13 @@
* and limitations under the License.
*/
-import { Stack } from "@aws-cdk/core";
+import { Stack } from '@aws-cdk/core';
import * as defaults from '../';
import * as ec2 from '@aws-cdk/aws-ec2';
import { SynthUtils } from '@aws-cdk/assert';
import '@aws-cdk/assert/jest';
-import { AddAwsServiceEndpoint, ServiceEndpointTypes } from "../lib/vpc-helper";
-import { DefaultPublicPrivateVpcProps, DefaultIsolatedVpcProps } from "../lib/vpc-defaults";
+import { AddAwsServiceEndpoint, ServiceEndpointTypes } from '../lib/vpc-helper';
+import { DefaultPublicPrivateVpcProps, DefaultIsolatedVpcProps } from '../lib/vpc-defaults';
// --------------------------------------------------------------
// Test minimal Public/Private deployment with no properties
@@ -27,7 +27,7 @@ test('Test minimal deployment with no properties', () => {
const stack = new Stack();
// Build VPC
defaults.buildVpc(stack, {
- defaultVpcProps: DefaultPublicPrivateVpcProps()
+ defaultVpcProps: DefaultPublicPrivateVpcProps(),
});
// Assertion
expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
@@ -41,19 +41,18 @@ test('Test minimal deployment with no properties', () => {
const stack = new Stack();
// Build VPC
defaults.buildVpc(stack, {
- defaultVpcProps: DefaultIsolatedVpcProps()
+ defaultVpcProps: DefaultIsolatedVpcProps(),
});
// Assertion
expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
- expect(SynthUtils.toCloudFormation(stack)).toHaveResource("AWS::EC2::VPC", {
+ expect(SynthUtils.toCloudFormation(stack)).toHaveResource('AWS::EC2::VPC', {
EnableDnsHostnames: true,
EnableDnsSupport: true,
});
- expect(SynthUtils.toCloudFormation(stack)).toCountResources("AWS::EC2::Subnet", 2);
- expect(SynthUtils.toCloudFormation(stack)).toCountResources("AWS::EC2::InternetGateway", 0);
-
+ expect(SynthUtils.toCloudFormation(stack)).toCountResources('AWS::EC2::Subnet', 2);
+ expect(SynthUtils.toCloudFormation(stack)).toCountResources('AWS::EC2::InternetGateway', 0);
});
// --------------------------------------------------------------
@@ -66,8 +65,8 @@ test('Test deployment w/ custom CIDR', () => {
defaults.buildVpc(stack, {
defaultVpcProps: DefaultPublicPrivateVpcProps(),
userVpcProps: {
- cidr: '172.168.0.0/16'
- }
+ cidr: '172.168.0.0/16',
+ },
});
// Assertion
expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
@@ -76,7 +75,7 @@ test('Test deployment w/ custom CIDR', () => {
// --------------------------------------------------------------
// Test deployment w/ user provided custom properties
// --------------------------------------------------------------
-test("Test deployment w/ user provided custom properties", () => {
+test('Test deployment w/ user provided custom properties', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -85,20 +84,20 @@ test("Test deployment w/ user provided custom properties", () => {
userVpcProps: {
enableDnsHostnames: false,
enableDnsSupport: false,
- cidr: '172.168.0.0/16'
- }
+ cidr: '172.168.0.0/16',
+ },
});
- expect(stack).toHaveResource("AWS::EC2::VPC", {
- CidrBlock: "172.168.0.0/16",
+ expect(stack).toHaveResource('AWS::EC2::VPC', {
+ CidrBlock: '172.168.0.0/16',
EnableDnsHostnames: false,
- EnableDnsSupport: false
+ EnableDnsSupport: false,
});
});
// --------------------------------------------------------------
// Test deployment w/ construct provided custom properties
// --------------------------------------------------------------
-test("Test deployment w/ construct provided custom properties", () => {
+test('Test deployment w/ construct provided custom properties', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -107,11 +106,11 @@ test("Test deployment w/ construct provided custom properties", () => {
constructVpcProps: {
enableDnsHostnames: true,
enableDnsSupport: true,
- cidr: "172.168.0.0/16",
+ cidr: '172.168.0.0/16',
},
});
- expect(stack).toHaveResource("AWS::EC2::VPC", {
- CidrBlock: "172.168.0.0/16",
+ expect(stack).toHaveResource('AWS::EC2::VPC', {
+ CidrBlock: '172.168.0.0/16',
EnableDnsHostnames: true,
EnableDnsSupport: true,
});
@@ -120,7 +119,7 @@ test("Test deployment w/ construct provided custom properties", () => {
// --------------------------------------------------------------
// Test deployment w/ construct and user provided custom properties
// --------------------------------------------------------------
-test("Test deployment w/ construct and user provided custom properties", () => {
+test('Test deployment w/ construct and user provided custom properties', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -129,16 +128,16 @@ test("Test deployment w/ construct and user provided custom properties", () => {
userVpcProps: {
enableDnsHostnames: false,
enableDnsSupport: false,
- cidr: "10.0.0.0/16",
+ cidr: '10.0.0.0/16',
},
constructVpcProps: {
enableDnsHostnames: false,
enableDnsSupport: false,
- cidr: "172.168.0.0/16",
+ cidr: '172.168.0.0/16',
},
});
- expect(stack).toHaveResource("AWS::EC2::VPC", {
- CidrBlock: "172.168.0.0/16",
+ expect(stack).toHaveResource('AWS::EC2::VPC', {
+ CidrBlock: '172.168.0.0/16',
EnableDnsHostnames: false,
EnableDnsSupport: false,
});
@@ -147,8 +146,7 @@ test("Test deployment w/ construct and user provided custom properties", () => {
// --------------------------------------------------------------
// Test priority of default, user and construct properties
// --------------------------------------------------------------
-test("Test deployment w/ construct and user provided custom properties", () => {
-
+test('Test deployment w/ construct and user provided custom properties', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -157,7 +155,7 @@ test("Test deployment w/ construct and user provided custom properties", () => {
userVpcProps: {
enableDnsHostnames: false,
enableDnsSupport: false,
- cidr: "10.0.0.0/16",
+ cidr: '10.0.0.0/16',
},
constructVpcProps: {
enableDnsHostnames: true,
@@ -166,7 +164,7 @@ test("Test deployment w/ construct and user provided custom properties", () => {
subnetConfiguration: [
{
cidrMask: 18,
- name: "isolated",
+ name: 'isolated',
subnetType: ec2.SubnetType.ISOLATED,
},
],
@@ -175,14 +173,14 @@ test("Test deployment w/ construct and user provided custom properties", () => {
AddAwsServiceEndpoint(stack, v, defaults.ServiceEndpointTypes.SQS);
// Expect 2 isolated subnets (usual error condition is 2 public/2 private)
- expect(stack).toCountResources("AWS::EC2::Subnet", 2);
- expect(stack).toCountResources("AWS::EC2::InternetGateway", 0);
+ expect(stack).toCountResources('AWS::EC2::Subnet', 2);
+ expect(stack).toCountResources('AWS::EC2::InternetGateway', 0);
});
// --------------------------------------------------------------
// Test deployment w/ existing VPC provided
// --------------------------------------------------------------
-test("Test deployment w/ existing VPC provided", () => {
+test('Test deployment w/ existing VPC provided', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -191,7 +189,7 @@ test("Test deployment w/ existing VPC provided", () => {
constructVpcProps: {
enableDnsHostnames: false,
enableDnsSupport: false,
- cidr: "172.168.0.0/16",
+ cidr: '172.168.0.0/16',
},
});
@@ -206,7 +204,7 @@ test("Test deployment w/ existing VPC provided", () => {
// --------------------------------------------------------------
// Test adding Gateway Endpoint
// --------------------------------------------------------------
-test("Test adding Gateway Endpoint", () => {
+test('Test adding Gateway Endpoint', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -217,15 +215,15 @@ test("Test adding Gateway Endpoint", () => {
AddAwsServiceEndpoint(stack, testVpc, ServiceEndpointTypes.DYNAMODB);
// Assertion
- expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", {
- VpcEndpointType: "Gateway",
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Gateway',
});
});
// --------------------------------------------------------------
// Test adding Interface Endpoint
// --------------------------------------------------------------
-test("Test adding Interface Endpoint", () => {
+test('Test adding Interface Endpoint', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -236,15 +234,34 @@ test("Test adding Interface Endpoint", () => {
AddAwsServiceEndpoint(stack, testVpc, ServiceEndpointTypes.SNS);
// Assertion
- expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", {
- VpcEndpointType: "Interface",
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Interface',
+ });
+});
+
+// --------------------------------------------------------------
+// Test adding SAGEMAKER_RUNTIME Interface Endpoint
+// --------------------------------------------------------------
+test('Test adding SAGEMAKER_RUNTIME Interface Endpoint', () => {
+ // Stack
+ const stack = new Stack();
+ // Build VPC
+ const testVpc = defaults.buildVpc(stack, {
+ defaultVpcProps: DefaultPublicPrivateVpcProps(),
+ });
+
+ AddAwsServiceEndpoint(stack, testVpc, ServiceEndpointTypes.SAGEMAKER_RUNTIME);
+
+ // Assertion
+ expect(stack).toHaveResource('AWS::EC2::VPCEndpoint', {
+ VpcEndpointType: 'Interface',
});
});
// --------------------------------------------------------------
// Test adding a second Endpoint of same service
// --------------------------------------------------------------
-test("Test adding a second Endpoint of same service", () => {
+test('Test adding a second Endpoint of same service', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -255,13 +272,13 @@ test("Test adding a second Endpoint of same service", () => {
AddAwsServiceEndpoint(stack, testVpc, ServiceEndpointTypes.SNS);
// Assertion
- expect(stack).toCountResources("AWS::EC2::VPCEndpoint", 1);
+ expect(stack).toCountResources('AWS::EC2::VPCEndpoint', 1);
});
// --------------------------------------------------------------
// Test adding bad Endpoint
// --------------------------------------------------------------
-test("Test adding bad Endpoint", () => {
+test('Test adding bad Endpoint', () => {
// Stack
const stack = new Stack();
// Build VPC
@@ -270,7 +287,7 @@ test("Test adding bad Endpoint", () => {
});
const app = () => {
- AddAwsServiceEndpoint(stack, testVpc, "string" as ServiceEndpointTypes);
+ AddAwsServiceEndpoint(stack, testVpc, 'string' as ServiceEndpointTypes);
};
// Assertion
expect(app).toThrowError();