├── .github └── workflows │ ├── build.yml │ └── update_snaphost.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── bin └── cdk.ts ├── cdk.json ├── imgs ├── architecture.png └── model-setup.png ├── jest.config.js ├── lib ├── constructs │ ├── alb.ts │ ├── dify-services │ │ ├── api.ts │ │ ├── docker │ │ │ ├── sandbox-python-requirements.txt │ │ │ └── sandbox.Dockerfile │ │ ├── web.ts │ │ └── worker.ts │ ├── postgres.ts │ └── redis.ts └── dify-on-aws-stack.ts ├── package-lock.json ├── package.json ├── test ├── __snapshots__ │ └── dify-on-aws.test.ts.snap └── dify-on-aws.test.ts └── tsconfig.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | workflow_dispatch: 5 | jobs: 6 | Build-and-Test-CDK: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use Node.js 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: "20.x" 14 | - run: | 15 | npm i 16 | npm run build 17 | npm run test 18 | working-directory: ./ 19 | -------------------------------------------------------------------------------- /.github/workflows/update_snaphost.yml: -------------------------------------------------------------------------------- 1 | name: Update snapshot 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - 'dependabot/**' 8 | 9 | jobs: 10 | update: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: "20.x" 18 | - run: | 19 | npm i 20 | npm run test -- -u 21 | working-directory: ./ 22 | - name: Add & Commit 23 | uses: EndBug/add-and-commit@v7.2.0 24 | with: 25 | add: "test/__snapshots__/." 26 | message: "update snapshot" 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | .tmp 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 tmokmss 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dify on AWS with CDK 2 | 3 | > [!IMPORTANT] 4 | > This repository is now moved to aws-samples organization. The new repository is here: [aws-samples/dify-self-hosted-on-aws](https://github.com/aws-samples/dify-self-hosted-on-aws). 5 | > 6 | > このレポジトリはaws-samples配下に移動しました。新しいレポジトリはこちらです: [aws-samples/dify-self-hosted-on-aws](https://github.com/aws-samples/dify-self-hosted-on-aws) 7 | -------------------------------------------------------------------------------- /bin/cdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { DifyOnAwsStack } from '../lib/dify-on-aws-stack'; 5 | import { AwsPrototypingChecks } from '@aws/pdk/pdk-nag'; 6 | 7 | const app = new cdk.App(); 8 | new DifyOnAwsStack(app, 'DifyOnAwsStack', { 9 | env: { 10 | region: 'ap-northeast-1', 11 | // You need to explicitly set AWS account ID when you look up an existing VPC. 12 | // account: '123456789012' 13 | }, 14 | // Allow access from the Internet. Narrow this down if you want further security. 15 | allowedCidrs: ['0.0.0.0/0'], 16 | difyImageTag: '0.8.3', 17 | }); 18 | 19 | // cdk.Aspects.of(app).add(new AwsPrototypingChecks()); 20 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 21 | "@aws-cdk/core:checkSecretUsage": true, 22 | "@aws-cdk/core:target-partitions": [ 23 | "aws", 24 | "aws-cn" 25 | ], 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 35 | "@aws-cdk/core:enablePartitionLiterals": true, 36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, 37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true, 38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, 39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, 40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, 41 | "@aws-cdk/aws-route53-patters:useCertificate": true, 42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false, 43 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, 44 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, 45 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, 46 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, 47 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, 48 | "@aws-cdk/aws-redshift:columnId": true, 49 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, 50 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, 51 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, 52 | "@aws-cdk/aws-kms:aliasNameRef": true, 53 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, 54 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, 55 | "@aws-cdk/aws-efs:denyAnonymousAccess": true, 56 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, 57 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, 58 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, 59 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, 60 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, 61 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, 62 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, 63 | "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, 64 | "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, 65 | "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, 66 | "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, 67 | "@aws-cdk/aws-eks:nodegroupNameAttribute": true, 68 | "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, 69 | "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, 70 | "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /imgs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmokmss/dify-on-aws-cdk/f35fd3497a60a9cfd78b8e77ad2e4a2dada05dbc/imgs/architecture.png -------------------------------------------------------------------------------- /imgs/model-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmokmss/dify-on-aws-cdk/f35fd3497a60a9cfd78b8e77ad2e4a2dada05dbc/imgs/model-setup.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /lib/constructs/alb.ts: -------------------------------------------------------------------------------- 1 | import { Duration } from 'aws-cdk-lib'; 2 | import { Certificate, CertificateValidation } from 'aws-cdk-lib/aws-certificatemanager'; 3 | import { IVpc, Peer, Port } from 'aws-cdk-lib/aws-ec2'; 4 | import { FargateService, IService } from 'aws-cdk-lib/aws-ecs'; 5 | import { 6 | ApplicationListener, 7 | ApplicationLoadBalancer, 8 | ApplicationProtocol, 9 | ApplicationTargetGroup, 10 | ListenerAction, 11 | ListenerCondition, 12 | } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; 13 | import { ARecord, IHostedZone, RecordTarget } from 'aws-cdk-lib/aws-route53'; 14 | import { LoadBalancerTarget } from 'aws-cdk-lib/aws-route53-targets'; 15 | import { Construct } from 'constructs'; 16 | 17 | export interface AlbProps { 18 | vpc: IVpc; 19 | allowedCidrs: string[]; 20 | 21 | /** 22 | * @default 'dify' 23 | */ 24 | subDomain?: string; 25 | 26 | /** 27 | * @default custom domain and TLS is not configured. 28 | */ 29 | hostedZone?: IHostedZone; 30 | } 31 | 32 | export class Alb extends Construct { 33 | public url: string; 34 | 35 | private listenerPriority = 1; 36 | private listener: ApplicationListener; 37 | private vpc: IVpc; 38 | 39 | constructor(scope: Construct, id: string, props: AlbProps) { 40 | super(scope, id); 41 | 42 | const { vpc, subDomain = 'dify' } = props; 43 | const protocol = props.hostedZone ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP; 44 | const certificate = props.hostedZone 45 | ? new Certificate(this, 'Certificate', { 46 | domainName: `${subDomain}.${props.hostedZone.zoneName}`, 47 | validation: CertificateValidation.fromDns(props.hostedZone), 48 | }) 49 | : undefined; 50 | 51 | const alb = new ApplicationLoadBalancer(this, 'Resource', { 52 | vpc, 53 | vpcSubnets: vpc.selectSubnets({ subnets: vpc.publicSubnets }), 54 | internetFacing: true, 55 | }); 56 | this.url = `${protocol.toLowerCase()}://${alb.loadBalancerDnsName}`; 57 | 58 | const listener = alb.addListener('Listener', { 59 | protocol, 60 | open: false, 61 | defaultAction: ListenerAction.fixedResponse(400), 62 | certificates: certificate ? [certificate] : undefined, 63 | }); 64 | props.allowedCidrs.forEach((cidr) => listener.connections.allowDefaultPortFrom(Peer.ipv4(cidr))); 65 | 66 | if (props.hostedZone) { 67 | new ARecord(this, 'AliasRecord', { 68 | zone: props.hostedZone, 69 | recordName: subDomain, 70 | target: RecordTarget.fromAlias(new LoadBalancerTarget(alb)), 71 | }); 72 | this.url = `${protocol.toLowerCase()}://${subDomain}.${props.hostedZone.zoneName}`; 73 | } 74 | 75 | this.vpc = vpc; 76 | this.listener = listener; 77 | } 78 | 79 | public addEcsService(id: string, ecsService: FargateService, port: number, healthCheckPath: string, paths: string[]) { 80 | const group = new ApplicationTargetGroup(this, `${id}TargetGroup`, { 81 | vpc: this.vpc, 82 | targets: [ecsService], 83 | protocol: ApplicationProtocol.HTTP, 84 | port: port, 85 | deregistrationDelay: Duration.seconds(10), 86 | healthCheck: { 87 | path: healthCheckPath, 88 | interval: Duration.seconds(15), 89 | healthyHttpCodes: '200-299,307', 90 | healthyThresholdCount: 2, 91 | unhealthyThresholdCount: 6, 92 | }, 93 | }); 94 | // a condition only accepts an array with up to 5 elements 95 | // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html 96 | for (let i = 0; i < Math.floor((paths.length + 4) / 5); i++) { 97 | const slice = paths.slice(i * 5, (i + 1) * 5); 98 | this.listener.addTargetGroups(`${id}${i}`, { 99 | targetGroups: [group], 100 | conditions: [ListenerCondition.pathPatterns(slice)], 101 | priority: this.listenerPriority++, 102 | }); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/constructs/dify-services/api.ts: -------------------------------------------------------------------------------- 1 | import { CpuArchitecture, FargateTaskDefinition, ICluster } from 'aws-cdk-lib/aws-ecs'; 2 | import { Construct } from 'constructs'; 3 | import { Duration, Stack, aws_ecs as ecs } from 'aws-cdk-lib'; 4 | import { Platform } from 'aws-cdk-lib/aws-ecr-assets'; 5 | import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; 6 | import { Postgres } from '../postgres'; 7 | import { Redis } from '../redis'; 8 | import { IBucket } from 'aws-cdk-lib/aws-s3'; 9 | import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; 10 | import { join } from 'path'; 11 | import { Alb } from '../alb'; 12 | 13 | export interface ApiServiceProps { 14 | cluster: ICluster; 15 | alb: Alb; 16 | 17 | postgres: Postgres; 18 | redis: Redis; 19 | storageBucket: IBucket; 20 | 21 | imageTag: string; 22 | sandboxImageTag: string; 23 | allowAnySyscalls: boolean; 24 | 25 | /** 26 | * If true, enable debug outputs 27 | * @default false 28 | */ 29 | debug?: boolean; 30 | } 31 | 32 | export class ApiService extends Construct { 33 | public readonly encryptionSecret: Secret; 34 | 35 | constructor(scope: Construct, id: string, props: ApiServiceProps) { 36 | super(scope, id); 37 | 38 | const { cluster, alb, postgres, redis, storageBucket, debug = false } = props; 39 | const port = 5001; 40 | 41 | const taskDefinition = new FargateTaskDefinition(this, 'Task', { 42 | cpu: 1024, 43 | // 512だとOOMが起きたので、増やした 44 | memoryLimitMiB: 2048, 45 | runtimePlatform: { cpuArchitecture: CpuArchitecture.X86_64 }, 46 | }); 47 | 48 | const encryptionSecret = new Secret(this, 'EncryptionSecret', { 49 | generateSecretString: { 50 | passwordLength: 42, 51 | }, 52 | }); 53 | this.encryptionSecret = encryptionSecret; 54 | 55 | taskDefinition.addContainer('Main', { 56 | image: ecs.ContainerImage.fromRegistry(`langgenius/dify-api:${props.imageTag}`), 57 | // https://docs.dify.ai/getting-started/install-self-hosted/environments 58 | environment: { 59 | MODE: 'api', 60 | // The log level for the application. Supported values are `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` 61 | LOG_LEVEL: debug ? 'DEBUG' : 'ERROR', 62 | // enable DEBUG mode to output more logs 63 | DEBUG: debug ? 'true' : 'false', 64 | 65 | // The base URL of console application web frontend, refers to the Console base URL of WEB service if console domain is 66 | // different from api or web app domain. 67 | CONSOLE_WEB_URL: alb.url, 68 | // The base URL of console application api server, refers to the Console base URL of WEB service if console domain is different from api or web app domain. 69 | CONSOLE_API_URL: alb.url, 70 | // The URL prefix for Service API endpoints, refers to the base URL of the current API service if api domain is different from console domain. 71 | SERVICE_API_URL: alb.url, 72 | // The URL prefix for Web APP frontend, refers to the Web App base URL of WEB service if web app domain is different from console or api domain. 73 | APP_WEB_URL: alb.url, 74 | 75 | // The configurations of redis connection. 76 | REDIS_HOST: redis.endpoint, 77 | REDIS_PORT: redis.port.toString(), 78 | REDIS_USE_SSL: 'true', 79 | REDIS_DB: '0', 80 | 81 | // Specifies the allowed origins for cross-origin requests to the Web API, e.g. https://dify.app or * for all origins. 82 | WEB_API_CORS_ALLOW_ORIGINS: '*', 83 | // Specifies the allowed origins for cross-origin requests to the console API, e.g. https://cloud.dify.ai or * for all origins. 84 | CONSOLE_CORS_ALLOW_ORIGINS: '*', 85 | 86 | // The type of storage to use for storing user files. 87 | STORAGE_TYPE: 's3', 88 | S3_BUCKET_NAME: storageBucket.bucketName, 89 | S3_REGION: Stack.of(storageBucket).region, 90 | 91 | // postgres settings. the credentials are in secrets property. 92 | DB_DATABASE: postgres.databaseName, 93 | 94 | // pgvector configurations 95 | VECTOR_STORE: 'pgvector', 96 | PGVECTOR_DATABASE: postgres.pgVectorDatabaseName, 97 | 98 | // The sandbox service endpoint. 99 | CODE_EXECUTION_ENDPOINT: 'http://localhost:8194', // Fargate の task 内通信は localhost 宛, 100 | }, 101 | logging: ecs.LogDriver.awsLogs({ 102 | streamPrefix: 'log', 103 | }), 104 | portMappings: [{ containerPort: port }], 105 | secrets: { 106 | // The configurations of postgres database connection. 107 | // It is consistent with the configuration in the 'db' service below. 108 | DB_USERNAME: ecs.Secret.fromSecretsManager(postgres.secret, 'username'), 109 | DB_HOST: ecs.Secret.fromSecretsManager(postgres.secret, 'host'), 110 | DB_PORT: ecs.Secret.fromSecretsManager(postgres.secret, 'port'), 111 | DB_PASSWORD: ecs.Secret.fromSecretsManager(postgres.secret, 'password'), 112 | PGVECTOR_USER: ecs.Secret.fromSecretsManager(postgres.secret, 'username'), 113 | PGVECTOR_HOST: ecs.Secret.fromSecretsManager(postgres.secret, 'host'), 114 | PGVECTOR_PORT: ecs.Secret.fromSecretsManager(postgres.secret, 'port'), 115 | PGVECTOR_PASSWORD: ecs.Secret.fromSecretsManager(postgres.secret, 'password'), 116 | REDIS_PASSWORD: ecs.Secret.fromSecretsManager(redis.secret), 117 | CELERY_BROKER_URL: ecs.Secret.fromSsmParameter(redis.brokerUrl), 118 | SECRET_KEY: ecs.Secret.fromSecretsManager(encryptionSecret), 119 | CODE_EXECUTION_API_KEY: ecs.Secret.fromSecretsManager(encryptionSecret), // is it ok to reuse this? 120 | }, 121 | healthCheck: { 122 | command: ['CMD-SHELL', `curl -f http://localhost:${port}/health || exit 1`], 123 | interval: Duration.seconds(15), 124 | startPeriod: Duration.seconds(30), 125 | timeout: Duration.seconds(5), 126 | retries: 5, 127 | }, 128 | }); 129 | 130 | taskDefinition.addContainer('Sandbox', { 131 | image: ecs.ContainerImage.fromAsset(join(__dirname, 'docker'), { 132 | file: 'sandbox.Dockerfile', 133 | platform: Platform.LINUX_AMD64, 134 | buildArgs: { 135 | DIFY_VERSION: props.sandboxImageTag, 136 | }, 137 | }), 138 | environment: { 139 | GIN_MODE: 'release', 140 | WORKER_TIMEOUT: '15', 141 | ENABLE_NETWORK: 'true', 142 | ...(props.allowAnySyscalls 143 | ? { 144 | ALLOWED_SYSCALLS: Array(457) 145 | .fill(0) 146 | .map((_, i) => i) 147 | .join(','), 148 | } 149 | : {}), 150 | PYTHON_LIB_PATH: [ 151 | // Originally from here: 152 | // https://github.com/langgenius/dify-sandbox/blob/main/internal/static/config_default_amd64.go 153 | '/usr/local/lib/python3.10', 154 | '/usr/lib/python3.10', 155 | '/usr/lib/python3', 156 | // copy all the lib. **DO NOT** add a trailing slash! 157 | '/usr/lib/x86_64-linux-gnu', 158 | '/etc/ssl/certs/ca-certificates.crt', 159 | '/etc/nsswitch.conf', 160 | '/etc/hosts', 161 | '/etc/resolv.conf', 162 | '/run/systemd/resolve/stub-resolv.conf', 163 | '/run/resolvconf/resolv.conf', 164 | ].join(','), 165 | }, 166 | logging: ecs.LogDriver.awsLogs({ 167 | streamPrefix: 'log', 168 | }), 169 | portMappings: [{ containerPort: 8194 }], 170 | secrets: { 171 | API_KEY: ecs.Secret.fromSecretsManager(encryptionSecret), 172 | }, 173 | }); 174 | 175 | storageBucket.grantReadWrite(taskDefinition.taskRole); 176 | 177 | // we can use IAM role once this issue will be closed 178 | // https://github.com/langgenius/dify/issues/3471 179 | taskDefinition.taskRole.addToPrincipalPolicy( 180 | new PolicyStatement({ 181 | actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'], 182 | resources: ['*'], 183 | }), 184 | ); 185 | 186 | // Service 187 | const service = new ecs.FargateService(this, 'FargateService', { 188 | cluster, 189 | taskDefinition, 190 | capacityProviderStrategies: [ 191 | { 192 | capacityProvider: 'FARGATE', 193 | weight: 0, 194 | }, 195 | { 196 | capacityProvider: 'FARGATE_SPOT', 197 | weight: 1, 198 | }, 199 | ], 200 | enableExecuteCommand: true, 201 | }); 202 | 203 | service.connections.allowToDefaultPort(postgres); 204 | service.connections.allowToDefaultPort(redis); 205 | 206 | const paths = ['/console/api', '/api', '/v1', '/files']; 207 | alb.addEcsService('Api', service, port, '/health', [...paths, ...paths.map((p) => `${p}/*`)]); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /lib/constructs/dify-services/docker/sandbox-python-requirements.txt: -------------------------------------------------------------------------------- 1 | # Here you can add Python packages you want to use in Dify sandbox. 2 | -------------------------------------------------------------------------------- /lib/constructs/dify-services/docker/sandbox.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DIFY_VERSION=latest 2 | FROM langgenius/dify-sandbox:${DIFY_VERSION} 3 | COPY ./sandbox-python-requirements.txt /dependencies/python-requirements.txt 4 | -------------------------------------------------------------------------------- /lib/constructs/dify-services/web.ts: -------------------------------------------------------------------------------- 1 | import { CpuArchitecture, FargateTaskDefinition, ICluster } from 'aws-cdk-lib/aws-ecs'; 2 | import { Construct } from 'constructs'; 3 | import { Duration, aws_ecs as ecs } from 'aws-cdk-lib'; 4 | import { Alb } from '../alb'; 5 | import { AlbController } from 'aws-cdk-lib/aws-eks'; 6 | 7 | export interface WebServiceProps { 8 | cluster: ICluster; 9 | alb: Alb; 10 | 11 | imageTag: string; 12 | 13 | /** 14 | * If true, enable debug outputs 15 | * @default false 16 | */ 17 | debug?: boolean; 18 | } 19 | 20 | export class WebService extends Construct { 21 | constructor(scope: Construct, id: string, props: WebServiceProps) { 22 | super(scope, id); 23 | 24 | const { cluster, alb, debug = false } = props; 25 | const port = 3000; 26 | 27 | const taskDefinition = new FargateTaskDefinition(this, 'Task', { 28 | cpu: 256, 29 | memoryLimitMiB: 512, 30 | runtimePlatform: { cpuArchitecture: CpuArchitecture.X86_64 }, 31 | }); 32 | 33 | taskDefinition.addContainer('Main', { 34 | image: ecs.ContainerImage.fromRegistry(`langgenius/dify-web:${props.imageTag}`), 35 | environment: { 36 | // The log level for the application. Supported values are `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` 37 | LOG_LEVEL: debug ? 'DEBUG' : 'ERROR', 38 | // enable DEBUG mode to output more logs 39 | DEBUG: debug ? 'true' : 'false', 40 | 41 | // The base URL of console application api server, refers to the Console base URL of WEB service if console domain is different from api or web app domain. 42 | // example: http://cloud.dify.ai 43 | CONSOLE_API_URL: alb.url, 44 | // The URL prefix for Web APP frontend, refers to the Web App base URL of WEB service if web app domain is different from console or api domain. 45 | // example: http://udify.app 46 | APP_API_URL: alb.url, 47 | 48 | // Setting host to 0.0.0.0 seems necessary for health check to pass. 49 | // https://nextjs.org/docs/pages/api-reference/next-config-js/output 50 | HOSTNAME: '0.0.0.0', 51 | PORT: port.toString(), 52 | }, 53 | logging: ecs.LogDriver.awsLogs({ 54 | streamPrefix: 'log', 55 | }), 56 | portMappings: [{ containerPort: port }], 57 | healthCheck: { 58 | // use wget instead of curl due to alpine: https://stackoverflow.com/a/47722899/18550269 59 | command: ['CMD-SHELL', `wget --no-verbose --tries=1 --spider http://localhost:${port}/ || exit 1`], 60 | interval: Duration.seconds(15), 61 | startPeriod: Duration.seconds(30), 62 | timeout: Duration.seconds(5), 63 | retries: 3, 64 | }, 65 | }); 66 | 67 | const service = new ecs.FargateService(this, 'FargateService', { 68 | cluster, 69 | taskDefinition, 70 | capacityProviderStrategies: [ 71 | { 72 | capacityProvider: 'FARGATE', 73 | weight: 0, 74 | }, 75 | { 76 | capacityProvider: 'FARGATE_SPOT', 77 | weight: 1, 78 | }, 79 | ], 80 | enableExecuteCommand: true, 81 | }); 82 | 83 | alb.addEcsService('Web', service, port, '/', ['/*']); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/constructs/dify-services/worker.ts: -------------------------------------------------------------------------------- 1 | import { CpuArchitecture, FargateTaskDefinition, ICluster } from 'aws-cdk-lib/aws-ecs'; 2 | import { Construct } from 'constructs'; 3 | import { Stack, aws_ecs as ecs } from 'aws-cdk-lib'; 4 | import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; 5 | import { Postgres } from '../postgres'; 6 | import { Redis } from '../redis'; 7 | import { IBucket } from 'aws-cdk-lib/aws-s3'; 8 | import { ISecret } from 'aws-cdk-lib/aws-secretsmanager'; 9 | 10 | export interface WorkerServiceProps { 11 | cluster: ICluster; 12 | 13 | postgres: Postgres; 14 | redis: Redis; 15 | storageBucket: IBucket; 16 | encryptionSecret: ISecret; 17 | 18 | imageTag: string; 19 | 20 | /** 21 | * If true, enable debug outputs 22 | * @default false 23 | */ 24 | debug?: boolean; 25 | } 26 | 27 | export class WorkerService extends Construct { 28 | constructor(scope: Construct, id: string, props: WorkerServiceProps) { 29 | super(scope, id); 30 | 31 | const { cluster, postgres, redis, storageBucket, debug = false } = props; 32 | 33 | const taskDefinition = new FargateTaskDefinition(this, 'Task', { 34 | cpu: 1024, 35 | memoryLimitMiB: 2048, 36 | runtimePlatform: { cpuArchitecture: CpuArchitecture.X86_64 }, 37 | }); 38 | 39 | taskDefinition.addContainer('Main', { 40 | image: ecs.ContainerImage.fromRegistry(`langgenius/dify-api:${props.imageTag}`), 41 | environment: { 42 | MODE: 'worker', 43 | // The log level for the application. Supported values are `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` 44 | LOG_LEVEL: debug ? 'DEBUG' : 'ERROR', 45 | // enable DEBUG mode to output more logs 46 | DEBUG: debug ? 'true' : 'false', 47 | 48 | // When enabled, migrations will be executed prior to application startup and the application will start after the migrations have completed. 49 | MIGRATION_ENABLED: 'true', 50 | 51 | // The configurations of redis connection. 52 | REDIS_HOST: redis.endpoint, 53 | REDIS_PORT: redis.port.toString(), 54 | REDIS_USE_SSL: 'true', 55 | REDIS_DB: '0', 56 | 57 | // The S3 storage configurations, only available when STORAGE_TYPE is `s3`. 58 | STORAGE_TYPE: 's3', 59 | S3_BUCKET_NAME: storageBucket.bucketName, 60 | S3_REGION: Stack.of(storageBucket).region, 61 | 62 | DB_DATABASE: postgres.databaseName, 63 | // pgvector configurations 64 | VECTOR_STORE: 'pgvector', 65 | PGVECTOR_DATABASE: postgres.pgVectorDatabaseName, 66 | }, 67 | logging: ecs.LogDriver.awsLogs({ 68 | streamPrefix: 'log', 69 | }), 70 | secrets: { 71 | DB_USERNAME: ecs.Secret.fromSecretsManager(postgres.secret, 'username'), 72 | DB_HOST: ecs.Secret.fromSecretsManager(postgres.secret, 'host'), 73 | DB_PORT: ecs.Secret.fromSecretsManager(postgres.secret, 'port'), 74 | DB_PASSWORD: ecs.Secret.fromSecretsManager(postgres.secret, 'password'), 75 | PGVECTOR_USER: ecs.Secret.fromSecretsManager(postgres.secret, 'username'), 76 | PGVECTOR_HOST: ecs.Secret.fromSecretsManager(postgres.secret, 'host'), 77 | PGVECTOR_PORT: ecs.Secret.fromSecretsManager(postgres.secret, 'port'), 78 | PGVECTOR_PASSWORD: ecs.Secret.fromSecretsManager(postgres.secret, 'password'), 79 | REDIS_PASSWORD: ecs.Secret.fromSecretsManager(redis.secret), 80 | CELERY_BROKER_URL: ecs.Secret.fromSsmParameter(redis.brokerUrl), 81 | SECRET_KEY: ecs.Secret.fromSecretsManager(props.encryptionSecret), 82 | }, 83 | }); 84 | storageBucket.grantReadWrite(taskDefinition.taskRole); 85 | 86 | taskDefinition.taskRole.addToPrincipalPolicy( 87 | new PolicyStatement({ 88 | actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'], 89 | resources: ['*'], 90 | }) 91 | ); 92 | 93 | const service = new ecs.FargateService(this, 'FargateService', { 94 | cluster, 95 | taskDefinition, 96 | capacityProviderStrategies: [ 97 | { 98 | capacityProvider: 'FARGATE', 99 | weight: 0, 100 | }, 101 | { 102 | capacityProvider: 'FARGATE_SPOT', 103 | weight: 1, 104 | }, 105 | ], 106 | enableExecuteCommand: true, 107 | }); 108 | 109 | service.connections.allowToDefaultPort(postgres); 110 | service.connections.allowToDefaultPort(redis); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lib/constructs/postgres.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import * as rds from 'aws-cdk-lib/aws-rds'; 3 | import * as ec2 from 'aws-cdk-lib/aws-ec2'; 4 | import { Connections, IConnectable, IVpc } from 'aws-cdk-lib/aws-ec2'; 5 | import { CfnOutput, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib'; 6 | import { ISecret } from 'aws-cdk-lib/aws-secretsmanager'; 7 | import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources'; 8 | import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; 9 | import { TimeSleep } from 'cdk-time-sleep'; 10 | 11 | export interface PostgresProps { 12 | vpc: IVpc; 13 | 14 | /** 15 | * If true, create an bastion instance. 16 | * @default false 17 | */ 18 | createBastion?: boolean; 19 | } 20 | 21 | export class Postgres extends Construct implements IConnectable { 22 | public readonly connections: Connections; 23 | public readonly cluster: rds.DatabaseCluster; 24 | public readonly secret: ISecret; 25 | public readonly databaseName = 'main'; 26 | public readonly pgVectorDatabaseName = 'pgvector'; 27 | 28 | private readonly queries: AwsCustomResource[] = []; 29 | private readonly writerId = 'Writer'; 30 | 31 | constructor(scope: Construct, id: string, props: PostgresProps) { 32 | super(scope, id); 33 | 34 | const { vpc } = props; 35 | 36 | const cluster = new rds.DatabaseCluster(this, 'Cluster', { 37 | engine: rds.DatabaseClusterEngine.auroraPostgres({ 38 | version: rds.AuroraPostgresEngineVersion.VER_15_5, 39 | }), 40 | vpc, 41 | serverlessV2MinCapacity: 0.5, 42 | serverlessV2MaxCapacity: 2.0, 43 | writer: rds.ClusterInstance.serverlessV2(this.writerId, { 44 | autoMinorVersionUpgrade: true, 45 | publiclyAccessible: false, 46 | }), 47 | defaultDatabaseName: this.databaseName, 48 | enableDataApi: true, 49 | storageEncrypted: true, 50 | removalPolicy: RemovalPolicy.DESTROY, 51 | }); 52 | 53 | if (props.createBastion) { 54 | const host = new ec2.BastionHostLinux(this, 'BastionHost', { 55 | vpc, 56 | machineImage: ec2.MachineImage.latestAmazonLinux2023({ cpuType: ec2.AmazonLinuxCpuType.ARM_64 }), 57 | instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO), 58 | blockDevices: [ 59 | { 60 | deviceName: '/dev/sdf', 61 | volume: ec2.BlockDeviceVolume.ebs(8, { 62 | encrypted: true, 63 | }), 64 | }, 65 | ], 66 | }); 67 | 68 | new CfnOutput(this, 'PortForwardCommand', { 69 | value: `aws ssm start-session --region ${Stack.of(this).region} --target ${ 70 | host.instanceId 71 | } --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"portNumber":["${ 72 | cluster.clusterEndpoint.port 73 | }"], "localPortNumber":["${cluster.clusterEndpoint.port}"], "host": ["${cluster.clusterEndpoint.hostname}"]}'`, 74 | }); 75 | new CfnOutput(this, 'DatabaseSecretsCommand', { 76 | value: `aws secretsmanager get-secret-value --secret-id ${cluster.secret!.secretName} --region ${Stack.of(this).region}`, 77 | }); 78 | } 79 | 80 | this.connections = cluster.connections; 81 | this.cluster = cluster; 82 | this.secret = cluster.secret!; 83 | 84 | this.runQuery(`CREATE DATABASE ${this.pgVectorDatabaseName};`, undefined); 85 | this.runQuery('CREATE EXTENSION IF NOT EXISTS vector;', this.pgVectorDatabaseName); 86 | } 87 | 88 | private runQuery(sql: string, database: string | undefined) { 89 | const cluster = this.cluster; 90 | const query = new AwsCustomResource(this, `Query${this.queries.length}`, { 91 | onUpdate: { 92 | // will also be called for a CREATE event 93 | service: 'rds-data', 94 | action: 'ExecuteStatement', 95 | parameters: { 96 | resourceArn: cluster.clusterArn, 97 | secretArn: cluster.secret!.secretArn, 98 | database: database, 99 | sql: sql, 100 | }, 101 | physicalResourceId: PhysicalResourceId.of(cluster.clusterArn), 102 | }, 103 | policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: [cluster.clusterArn] }), 104 | }); 105 | cluster.secret!.grantRead(query); 106 | cluster.grantDataApiAccess(query); 107 | if (this.queries.length > 0) { 108 | // We assume each query must be called serially, not in parallel. 109 | query.node.defaultChild!.node.addDependency(this.queries.at(-1)!.node.defaultChild!); 110 | } else { 111 | // When the Data API is called immediately after the writer creation, we got the below error: 112 | // > Message returned: HttpEndpoint is not enabled for resource ... 113 | // So we wait a minute after the creation before the first Data API call. 114 | const sleep = new TimeSleep(this, 'WaitForHttpEndpointReady', { 115 | createDuration: Duration.seconds(60), 116 | }); 117 | const dbInstance = this.cluster.node.findChild(this.writerId).node.defaultChild!; 118 | sleep.node.defaultChild!.node.addDependency(dbInstance); 119 | query.node.defaultChild!.node.addDependency(sleep); 120 | } 121 | this.queries.push(query); 122 | return query; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/constructs/redis.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import * as ec2 from 'aws-cdk-lib/aws-ec2'; 3 | import { CfnReplicationGroup, CfnSubnetGroup } from 'aws-cdk-lib/aws-elasticache'; 4 | import { SecurityGroup } from 'aws-cdk-lib/aws-ec2'; 5 | import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; 6 | import { StringParameter } from 'aws-cdk-lib/aws-ssm'; 7 | 8 | export interface RedisProps { 9 | vpc: ec2.IVpc; 10 | multiAz: boolean; 11 | } 12 | 13 | export class Redis extends Construct implements ec2.IConnectable { 14 | readonly endpoint: string; 15 | public connections: ec2.Connections; 16 | public readonly secret: Secret; 17 | public readonly port: number = 6379; 18 | public readonly brokerUrl: StringParameter; 19 | 20 | constructor(scope: Construct, id: string, props: RedisProps) { 21 | super(scope, id); 22 | 23 | const { vpc, multiAz } = props; 24 | 25 | const subnetGroup = new CfnSubnetGroup(this, 'SubnetGroup', { 26 | subnetIds: vpc.privateSubnets.map(({ subnetId }) => subnetId), 27 | description: 'private subnet', 28 | }); 29 | 30 | const securityGroup = new SecurityGroup(this, 'SecurityGroup', { 31 | vpc, 32 | }); 33 | 34 | const secret = new Secret(this, 'AuthToken', { 35 | generateSecretString: { 36 | passwordLength: 30, 37 | excludePunctuation: true, 38 | }, 39 | }); 40 | 41 | const redis = new CfnReplicationGroup(this, 'Resource', { 42 | engine: 'Redis', 43 | cacheNodeType: 'cache.t4g.micro', 44 | engineVersion: '7.1', 45 | port: this.port, 46 | replicasPerNodeGroup: multiAz ? 1 : 0, 47 | numNodeGroups: 1, 48 | replicationGroupDescription: 'Dify redis cluster', 49 | cacheSubnetGroupName: subnetGroup.ref, 50 | automaticFailoverEnabled: multiAz, 51 | multiAzEnabled: multiAz, 52 | securityGroupIds: [securityGroup.securityGroupId], 53 | transitEncryptionEnabled: true, 54 | atRestEncryptionEnabled: true, 55 | authToken: secret.secretValue.unsafeUnwrap(), 56 | }); 57 | 58 | this.endpoint = redis.attrPrimaryEndPointAddress; 59 | 60 | this.brokerUrl = new StringParameter(this, 'BrokerUrl', { 61 | stringValue: `rediss://:${secret.secretValue.unsafeUnwrap()}@${this.endpoint}:${this.port}/1`, 62 | }); 63 | 64 | this.connections = new ec2.Connections({ securityGroups: [securityGroup], defaultPort: ec2.Port.tcp(this.port) }); 65 | this.secret = secret; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/dify-on-aws-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { 3 | AmazonLinuxCpuType, 4 | IVpc, 5 | InstanceClass, 6 | InstanceSize, 7 | InstanceType, 8 | MachineImage, 9 | NatProvider, 10 | SubnetType, 11 | Vpc, 12 | } from 'aws-cdk-lib/aws-ec2'; 13 | import { Cluster } from 'aws-cdk-lib/aws-ecs'; 14 | import { Construct } from 'constructs'; 15 | import { Postgres } from './constructs/postgres'; 16 | import { Redis } from './constructs/redis'; 17 | import { BlockPublicAccess, Bucket } from 'aws-cdk-lib/aws-s3'; 18 | import { WebService } from './constructs/dify-services/web'; 19 | import { ApiService } from './constructs/dify-services/api'; 20 | import { WorkerService } from './constructs/dify-services/worker'; 21 | import { Alb } from './constructs/alb'; 22 | import { PublicHostedZone } from 'aws-cdk-lib/aws-route53'; 23 | 24 | interface DifyOnAwsStackProps extends cdk.StackProps { 25 | /** 26 | * The IP address ranges in CIDR notation that have access to the app. 27 | * @example ['1.1.1.1/30'] 28 | */ 29 | allowedCidrs: string[]; 30 | 31 | /** 32 | * Use t4g.nano NAT instances instead of NAT Gateway. 33 | * Ignored when you import an existing VPC. 34 | * @default false 35 | */ 36 | cheapVpc?: boolean; 37 | 38 | /** 39 | * If set, it imports the existing VPC instead of creating a new one. 40 | * The VPC must have one or more public and private subnets. 41 | * @default create a new VPC 42 | */ 43 | vpcId?: string; 44 | 45 | /** 46 | * The domain name you use for Dify's service URL. 47 | * You must own a Route53 public hosted zone for the domain in your account. 48 | * @default No custom domain is used. 49 | */ 50 | domainName?: string; 51 | 52 | /** 53 | * The ID of Route53 hosted zone for the domain. 54 | * @default No custom domain is used. 55 | */ 56 | hostedZoneId?: string; 57 | 58 | /** 59 | * If true, the ElastiCache Redis cluster is deployed to multiple AZs for fault tolerance. 60 | * It is generally recommended to enable this, but you can disable it to minimize AWS cost. 61 | * @default true 62 | */ 63 | isRedisMultiAz?: boolean; 64 | 65 | /** 66 | * The image tag to deploy Dify container images (api=worker and web). 67 | * The images are pulled from [here](https://hub.docker.com/u/langgenius). 68 | * 69 | * It is recommended to set this to a fixed version, 70 | * because otherwise an unexpected version is pulled on a ECS service's scaling activity. 71 | * @default "latest" 72 | */ 73 | difyImageTag?: string; 74 | 75 | /** 76 | * The image tag to deploy the Dify sandbox container image. 77 | * The image is pulled from [here](https://hub.docker.com/r/langgenius/dify-sandbox/tags). 78 | * 79 | * @default "latest" 80 | */ 81 | difySandboxImageTag?: string; 82 | 83 | /** 84 | * If true, Dify sandbox allows any system calls when executing code. 85 | * Do NOT set this property if you are not sure code executed in the sandbox 86 | * can be trusted or not. 87 | * 88 | * @default false 89 | */ 90 | allowAnySyscalls?: boolean; 91 | } 92 | 93 | export class DifyOnAwsStack extends cdk.Stack { 94 | constructor(scope: Construct, id: string, props: DifyOnAwsStackProps) { 95 | super(scope, id, props); 96 | 97 | const { 98 | difyImageTag: imageTag = 'latest', 99 | difySandboxImageTag: sandboxImageTag = 'latest', 100 | allowAnySyscalls = false, 101 | } = props; 102 | 103 | let vpc: IVpc; 104 | if (props.vpcId != null) { 105 | vpc = Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId }); 106 | } else { 107 | vpc = new Vpc(this, 'Vpc', { 108 | ...(props.cheapVpc 109 | ? { 110 | natGatewayProvider: NatProvider.instanceV2({ 111 | instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.NANO), 112 | }), 113 | natGateways: 1, 114 | } 115 | : {}), 116 | maxAzs: 2, 117 | subnetConfiguration: [ 118 | { 119 | subnetType: SubnetType.PUBLIC, 120 | name: 'Public', 121 | mapPublicIpOnLaunch: false, 122 | }, 123 | { 124 | subnetType: SubnetType.PRIVATE_WITH_EGRESS, 125 | name: 'Private', 126 | }, 127 | ], 128 | }); 129 | } 130 | 131 | if ((props.hostedZoneId != null) !== (props.domainName != null)) { 132 | throw new Error(`You have to set both hostedZoneId and domainName! Or leave both blank.`); 133 | } 134 | 135 | const hostedZone = 136 | props.domainName && props.hostedZoneId 137 | ? PublicHostedZone.fromHostedZoneAttributes(this, 'HostedZone', { 138 | zoneName: props.domainName, 139 | hostedZoneId: props.hostedZoneId, 140 | }) 141 | : undefined; 142 | 143 | const cluster = new Cluster(this, 'Cluster', { 144 | vpc, 145 | containerInsights: true, 146 | }); 147 | 148 | const postgres = new Postgres(this, 'Postgres', { 149 | vpc, 150 | }); 151 | 152 | const redis = new Redis(this, 'Redis', { vpc, multiAz: props.isRedisMultiAz ?? true }); 153 | 154 | const storageBucket = new Bucket(this, 'StorageBucket', { 155 | autoDeleteObjects: true, 156 | enforceSSL: true, 157 | removalPolicy: cdk.RemovalPolicy.DESTROY, 158 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 159 | }); 160 | 161 | const alb = new Alb(this, 'Alb', { vpc, allowedCidrs: props.allowedCidrs, hostedZone }); 162 | 163 | const api = new ApiService(this, 'ApiService', { 164 | cluster, 165 | alb, 166 | postgres, 167 | redis, 168 | storageBucket, 169 | imageTag, 170 | sandboxImageTag, 171 | allowAnySyscalls, 172 | }); 173 | 174 | new WebService(this, 'WebService', { 175 | cluster, 176 | alb, 177 | imageTag, 178 | }); 179 | 180 | new WorkerService(this, 'WorkerService', { 181 | cluster, 182 | postgres, 183 | redis, 184 | storageBucket, 185 | encryptionSecret: api.encryptionSecret, 186 | imageTag, 187 | }); 188 | 189 | new cdk.CfnOutput(this, 'DifyUrl', { 190 | value: alb.url, 191 | }); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dify-on-aws-cdk", 3 | "version": "0.1.0", 4 | "bin": { 5 | "dify-on-aws-cdk": "bin/cdk.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^29.5.12", 15 | "@types/node": "20.12.7", 16 | "aws-cdk": "^2.145.0", 17 | "esbuild": "^0.21.5", 18 | "jest": "^29.7.0", 19 | "ts-jest": "^29.1.2", 20 | "ts-node": "^10.9.2", 21 | "typescript": "~5.4.5" 22 | }, 23 | "dependencies": { 24 | "@aws/pdk": "^0.23.48", 25 | "@types/aws-lambda": "^8.10.138", 26 | "aws-cdk-lib": "^2.145.0", 27 | "cdk-time-sleep": "^0.0.5", 28 | "constructs": "^10.0.0", 29 | "source-map-support": "^0.5.21" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/__snapshots__/dify-on-aws.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Snapshot test 1`] = ` 4 | { 5 | "Mappings": { 6 | "LatestNodeRuntimeMap": { 7 | "af-south-1": { 8 | "value": "nodejs20.x", 9 | }, 10 | "ap-east-1": { 11 | "value": "nodejs20.x", 12 | }, 13 | "ap-northeast-1": { 14 | "value": "nodejs20.x", 15 | }, 16 | "ap-northeast-2": { 17 | "value": "nodejs20.x", 18 | }, 19 | "ap-northeast-3": { 20 | "value": "nodejs20.x", 21 | }, 22 | "ap-south-1": { 23 | "value": "nodejs20.x", 24 | }, 25 | "ap-south-2": { 26 | "value": "nodejs20.x", 27 | }, 28 | "ap-southeast-1": { 29 | "value": "nodejs20.x", 30 | }, 31 | "ap-southeast-2": { 32 | "value": "nodejs20.x", 33 | }, 34 | "ap-southeast-3": { 35 | "value": "nodejs20.x", 36 | }, 37 | "ap-southeast-4": { 38 | "value": "nodejs20.x", 39 | }, 40 | "ca-central-1": { 41 | "value": "nodejs20.x", 42 | }, 43 | "cn-north-1": { 44 | "value": "nodejs18.x", 45 | }, 46 | "cn-northwest-1": { 47 | "value": "nodejs18.x", 48 | }, 49 | "eu-central-1": { 50 | "value": "nodejs20.x", 51 | }, 52 | "eu-central-2": { 53 | "value": "nodejs20.x", 54 | }, 55 | "eu-north-1": { 56 | "value": "nodejs20.x", 57 | }, 58 | "eu-south-1": { 59 | "value": "nodejs20.x", 60 | }, 61 | "eu-south-2": { 62 | "value": "nodejs20.x", 63 | }, 64 | "eu-west-1": { 65 | "value": "nodejs20.x", 66 | }, 67 | "eu-west-2": { 68 | "value": "nodejs20.x", 69 | }, 70 | "eu-west-3": { 71 | "value": "nodejs20.x", 72 | }, 73 | "il-central-1": { 74 | "value": "nodejs20.x", 75 | }, 76 | "me-central-1": { 77 | "value": "nodejs20.x", 78 | }, 79 | "me-south-1": { 80 | "value": "nodejs20.x", 81 | }, 82 | "sa-east-1": { 83 | "value": "nodejs20.x", 84 | }, 85 | "us-east-1": { 86 | "value": "nodejs20.x", 87 | }, 88 | "us-east-2": { 89 | "value": "nodejs20.x", 90 | }, 91 | "us-gov-east-1": { 92 | "value": "nodejs18.x", 93 | }, 94 | "us-gov-west-1": { 95 | "value": "nodejs18.x", 96 | }, 97 | "us-iso-east-1": { 98 | "value": "nodejs18.x", 99 | }, 100 | "us-iso-west-1": { 101 | "value": "nodejs18.x", 102 | }, 103 | "us-isob-east-1": { 104 | "value": "nodejs18.x", 105 | }, 106 | "us-west-1": { 107 | "value": "nodejs20.x", 108 | }, 109 | "us-west-2": { 110 | "value": "nodejs20.x", 111 | }, 112 | }, 113 | }, 114 | "Outputs": { 115 | "DifyUrl": { 116 | "Value": "https://dify.example.com", 117 | }, 118 | }, 119 | "Parameters": { 120 | "BootstrapVersion": { 121 | "Default": "/cdk-bootstrap/hnb659fds/version", 122 | "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", 123 | "Type": "AWS::SSM::Parameter::Value", 124 | }, 125 | }, 126 | "Resources": { 127 | "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { 128 | "DependsOn": [ 129 | "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", 130 | "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", 131 | ], 132 | "Properties": { 133 | "Code": { 134 | "S3Bucket": { 135 | "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", 136 | }, 137 | "S3Key": "97f30e67419a1676a2215492723e5add1aa491caf0cbe2dd878fc4fab0468cd4.zip", 138 | }, 139 | "Handler": "index.handler", 140 | "Role": { 141 | "Fn::GetAtt": [ 142 | "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", 143 | "Arn", 144 | ], 145 | }, 146 | "Runtime": { 147 | "Fn::FindInMap": [ 148 | "LatestNodeRuntimeMap", 149 | { 150 | "Ref": "AWS::Region", 151 | }, 152 | "value", 153 | ], 154 | }, 155 | "Timeout": 120, 156 | }, 157 | "Type": "AWS::Lambda::Function", 158 | }, 159 | "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { 160 | "Properties": { 161 | "AssumeRolePolicyDocument": { 162 | "Statement": [ 163 | { 164 | "Action": "sts:AssumeRole", 165 | "Effect": "Allow", 166 | "Principal": { 167 | "Service": "lambda.amazonaws.com", 168 | }, 169 | }, 170 | ], 171 | "Version": "2012-10-17", 172 | }, 173 | "ManagedPolicyArns": [ 174 | { 175 | "Fn::Join": [ 176 | "", 177 | [ 178 | "arn:", 179 | { 180 | "Ref": "AWS::Partition", 181 | }, 182 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 183 | ], 184 | ], 185 | }, 186 | ], 187 | }, 188 | "Type": "AWS::IAM::Role", 189 | }, 190 | "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E": { 191 | "Properties": { 192 | "PolicyDocument": { 193 | "Statement": [ 194 | { 195 | "Action": [ 196 | "secretsmanager:GetSecretValue", 197 | "secretsmanager:DescribeSecret", 198 | ], 199 | "Effect": "Allow", 200 | "Resource": { 201 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 202 | }, 203 | }, 204 | { 205 | "Action": [ 206 | "rds-data:BatchExecuteStatement", 207 | "rds-data:BeginTransaction", 208 | "rds-data:CommitTransaction", 209 | "rds-data:ExecuteStatement", 210 | "rds-data:RollbackTransaction", 211 | ], 212 | "Effect": "Allow", 213 | "Resource": { 214 | "Fn::Join": [ 215 | "", 216 | [ 217 | "arn:", 218 | { 219 | "Ref": "AWS::Partition", 220 | }, 221 | ":rds:", 222 | { 223 | "Ref": "AWS::Region", 224 | }, 225 | ":", 226 | { 227 | "Ref": "AWS::AccountId", 228 | }, 229 | ":cluster:", 230 | { 231 | "Ref": "PostgresCluster53E5BDAB", 232 | }, 233 | ], 234 | ], 235 | }, 236 | }, 237 | ], 238 | "Version": "2012-10-17", 239 | }, 240 | "PolicyName": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E", 241 | "Roles": [ 242 | { 243 | "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", 244 | }, 245 | ], 246 | }, 247 | "Type": "AWS::IAM::Policy", 248 | }, 249 | "AlbAliasRecordEE9D42DE": { 250 | "Properties": { 251 | "AliasTarget": { 252 | "DNSName": { 253 | "Fn::Join": [ 254 | "", 255 | [ 256 | "dualstack.", 257 | { 258 | "Fn::GetAtt": [ 259 | "AlbC1372A32", 260 | "DNSName", 261 | ], 262 | }, 263 | ], 264 | ], 265 | }, 266 | "HostedZoneId": { 267 | "Fn::GetAtt": [ 268 | "AlbC1372A32", 269 | "CanonicalHostedZoneID", 270 | ], 271 | }, 272 | }, 273 | "HostedZoneId": "Z0123456789ABCDEFG", 274 | "Name": "dify.example.com.", 275 | "Type": "A", 276 | }, 277 | "Type": "AWS::Route53::RecordSet", 278 | }, 279 | "AlbApiTargetGroup4B6AF19C": { 280 | "Properties": { 281 | "HealthCheckIntervalSeconds": 15, 282 | "HealthCheckPath": "/health", 283 | "HealthyThresholdCount": 2, 284 | "Matcher": { 285 | "HttpCode": "200-299,307", 286 | }, 287 | "Port": 5001, 288 | "Protocol": "HTTP", 289 | "TargetGroupAttributes": [ 290 | { 291 | "Key": "deregistration_delay.timeout_seconds", 292 | "Value": "10", 293 | }, 294 | { 295 | "Key": "stickiness.enabled", 296 | "Value": "false", 297 | }, 298 | ], 299 | "TargetType": "ip", 300 | "UnhealthyThresholdCount": 6, 301 | "VpcId": { 302 | "Ref": "Vpc8378EB38", 303 | }, 304 | }, 305 | "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", 306 | }, 307 | "AlbC1372A32": { 308 | "DependsOn": [ 309 | "VpcPublicSubnet1DefaultRoute3DA9E72A", 310 | "VpcPublicSubnet1RouteTableAssociation97140677", 311 | "VpcPublicSubnet2DefaultRoute97F91067", 312 | "VpcPublicSubnet2RouteTableAssociationDD5762D8", 313 | ], 314 | "Properties": { 315 | "LoadBalancerAttributes": [ 316 | { 317 | "Key": "deletion_protection.enabled", 318 | "Value": "false", 319 | }, 320 | ], 321 | "Scheme": "internet-facing", 322 | "SecurityGroups": [ 323 | { 324 | "Fn::GetAtt": [ 325 | "AlbSecurityGroup433229ED", 326 | "GroupId", 327 | ], 328 | }, 329 | ], 330 | "Subnets": [ 331 | { 332 | "Ref": "VpcPublicSubnet1Subnet5C2D37C4", 333 | }, 334 | { 335 | "Ref": "VpcPublicSubnet2Subnet691E08A3", 336 | }, 337 | ], 338 | "Type": "application", 339 | }, 340 | "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", 341 | }, 342 | "AlbCertificate78F220B5": { 343 | "Properties": { 344 | "DomainName": "dify.example.com", 345 | "DomainValidationOptions": [ 346 | { 347 | "DomainName": "dify.example.com", 348 | "HostedZoneId": "Z0123456789ABCDEFG", 349 | }, 350 | ], 351 | "Tags": [ 352 | { 353 | "Key": "Name", 354 | "Value": "TestStack/Alb/Certificate", 355 | }, 356 | ], 357 | "ValidationMethod": "DNS", 358 | }, 359 | "Type": "AWS::CertificateManager::Certificate", 360 | }, 361 | "AlbListener318AEEBA": { 362 | "Properties": { 363 | "Certificates": [ 364 | { 365 | "CertificateArn": { 366 | "Ref": "AlbCertificate78F220B5", 367 | }, 368 | }, 369 | ], 370 | "DefaultActions": [ 371 | { 372 | "FixedResponseConfig": { 373 | "StatusCode": "400", 374 | }, 375 | "Type": "fixed-response", 376 | }, 377 | ], 378 | "LoadBalancerArn": { 379 | "Ref": "AlbC1372A32", 380 | }, 381 | "Port": 443, 382 | "Protocol": "HTTPS", 383 | }, 384 | "Type": "AWS::ElasticLoadBalancingV2::Listener", 385 | }, 386 | "AlbListenerApi0Rule033B7A48": { 387 | "Properties": { 388 | "Actions": [ 389 | { 390 | "TargetGroupArn": { 391 | "Ref": "AlbApiTargetGroup4B6AF19C", 392 | }, 393 | "Type": "forward", 394 | }, 395 | ], 396 | "Conditions": [ 397 | { 398 | "Field": "path-pattern", 399 | "PathPatternConfig": { 400 | "Values": [ 401 | "/console/api", 402 | "/api", 403 | "/v1", 404 | "/files", 405 | "/console/api/*", 406 | ], 407 | }, 408 | }, 409 | ], 410 | "ListenerArn": { 411 | "Ref": "AlbListener318AEEBA", 412 | }, 413 | "Priority": 1, 414 | }, 415 | "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", 416 | }, 417 | "AlbListenerApi1RuleDF535F10": { 418 | "Properties": { 419 | "Actions": [ 420 | { 421 | "TargetGroupArn": { 422 | "Ref": "AlbApiTargetGroup4B6AF19C", 423 | }, 424 | "Type": "forward", 425 | }, 426 | ], 427 | "Conditions": [ 428 | { 429 | "Field": "path-pattern", 430 | "PathPatternConfig": { 431 | "Values": [ 432 | "/api/*", 433 | "/v1/*", 434 | "/files/*", 435 | ], 436 | }, 437 | }, 438 | ], 439 | "ListenerArn": { 440 | "Ref": "AlbListener318AEEBA", 441 | }, 442 | "Priority": 2, 443 | }, 444 | "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", 445 | }, 446 | "AlbListenerWeb0RuleE10BEE0F": { 447 | "Properties": { 448 | "Actions": [ 449 | { 450 | "TargetGroupArn": { 451 | "Ref": "AlbWebTargetGroupC65B2BDF", 452 | }, 453 | "Type": "forward", 454 | }, 455 | ], 456 | "Conditions": [ 457 | { 458 | "Field": "path-pattern", 459 | "PathPatternConfig": { 460 | "Values": [ 461 | "/*", 462 | ], 463 | }, 464 | }, 465 | ], 466 | "ListenerArn": { 467 | "Ref": "AlbListener318AEEBA", 468 | }, 469 | "Priority": 3, 470 | }, 471 | "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", 472 | }, 473 | "AlbSecurityGroup433229ED": { 474 | "Properties": { 475 | "GroupDescription": "Automatically created Security Group for ELB TestStackAlb4BAF7F63", 476 | "SecurityGroupIngress": [ 477 | { 478 | "CidrIp": "0.0.0.0/0", 479 | "Description": "from 0.0.0.0/0:443", 480 | "FromPort": 443, 481 | "IpProtocol": "tcp", 482 | "ToPort": 443, 483 | }, 484 | ], 485 | "VpcId": { 486 | "Ref": "Vpc8378EB38", 487 | }, 488 | }, 489 | "Type": "AWS::EC2::SecurityGroup", 490 | }, 491 | "AlbSecurityGrouptoTestStackApiServiceFargateServiceSecurityGroup7DD0AF445001CF7EB3A2": { 492 | "Properties": { 493 | "Description": "Load balancer to target", 494 | "DestinationSecurityGroupId": { 495 | "Fn::GetAtt": [ 496 | "ApiServiceFargateServiceSecurityGroupE31C96C6", 497 | "GroupId", 498 | ], 499 | }, 500 | "FromPort": 5001, 501 | "GroupId": { 502 | "Fn::GetAtt": [ 503 | "AlbSecurityGroup433229ED", 504 | "GroupId", 505 | ], 506 | }, 507 | "IpProtocol": "tcp", 508 | "ToPort": 5001, 509 | }, 510 | "Type": "AWS::EC2::SecurityGroupEgress", 511 | }, 512 | "AlbSecurityGrouptoTestStackWebServiceFargateServiceSecurityGroup424456D03000D480A23C": { 513 | "Properties": { 514 | "Description": "Load balancer to target", 515 | "DestinationSecurityGroupId": { 516 | "Fn::GetAtt": [ 517 | "WebServiceFargateServiceSecurityGroup60FE78D6", 518 | "GroupId", 519 | ], 520 | }, 521 | "FromPort": 3000, 522 | "GroupId": { 523 | "Fn::GetAtt": [ 524 | "AlbSecurityGroup433229ED", 525 | "GroupId", 526 | ], 527 | }, 528 | "IpProtocol": "tcp", 529 | "ToPort": 3000, 530 | }, 531 | "Type": "AWS::EC2::SecurityGroupEgress", 532 | }, 533 | "AlbWebTargetGroupC65B2BDF": { 534 | "Properties": { 535 | "HealthCheckIntervalSeconds": 15, 536 | "HealthCheckPath": "/", 537 | "HealthyThresholdCount": 2, 538 | "Matcher": { 539 | "HttpCode": "200-299,307", 540 | }, 541 | "Port": 3000, 542 | "Protocol": "HTTP", 543 | "TargetGroupAttributes": [ 544 | { 545 | "Key": "deregistration_delay.timeout_seconds", 546 | "Value": "10", 547 | }, 548 | { 549 | "Key": "stickiness.enabled", 550 | "Value": "false", 551 | }, 552 | ], 553 | "TargetType": "ip", 554 | "UnhealthyThresholdCount": 6, 555 | "VpcId": { 556 | "Ref": "Vpc8378EB38", 557 | }, 558 | }, 559 | "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", 560 | }, 561 | "ApiServiceEncryptionSecretF73F9ECD": { 562 | "DeletionPolicy": "Delete", 563 | "Properties": { 564 | "GenerateSecretString": { 565 | "PasswordLength": 42, 566 | }, 567 | }, 568 | "Type": "AWS::SecretsManager::Secret", 569 | "UpdateReplacePolicy": "Delete", 570 | }, 571 | "ApiServiceFargateServiceE4EA9E4E": { 572 | "DependsOn": [ 573 | "AlbListenerApi0Rule033B7A48", 574 | "AlbListenerApi1RuleDF535F10", 575 | "ApiServiceTaskTaskRoleDefaultPolicy982AD2DC", 576 | "ApiServiceTaskTaskRole06F87EBE", 577 | ], 578 | "Properties": { 579 | "CapacityProviderStrategy": [ 580 | { 581 | "CapacityProvider": "FARGATE", 582 | "Weight": 0, 583 | }, 584 | { 585 | "CapacityProvider": "FARGATE_SPOT", 586 | "Weight": 1, 587 | }, 588 | ], 589 | "Cluster": { 590 | "Ref": "ClusterEB0386A7", 591 | }, 592 | "DeploymentConfiguration": { 593 | "Alarms": { 594 | "AlarmNames": [], 595 | "Enable": false, 596 | "Rollback": false, 597 | }, 598 | "MaximumPercent": 200, 599 | "MinimumHealthyPercent": 50, 600 | }, 601 | "EnableECSManagedTags": false, 602 | "EnableExecuteCommand": true, 603 | "HealthCheckGracePeriodSeconds": 60, 604 | "LoadBalancers": [ 605 | { 606 | "ContainerName": "Main", 607 | "ContainerPort": 5001, 608 | "TargetGroupArn": { 609 | "Ref": "AlbApiTargetGroup4B6AF19C", 610 | }, 611 | }, 612 | ], 613 | "NetworkConfiguration": { 614 | "AwsvpcConfiguration": { 615 | "AssignPublicIp": "DISABLED", 616 | "SecurityGroups": [ 617 | { 618 | "Fn::GetAtt": [ 619 | "ApiServiceFargateServiceSecurityGroupE31C96C6", 620 | "GroupId", 621 | ], 622 | }, 623 | ], 624 | "Subnets": [ 625 | { 626 | "Ref": "VpcPrivateSubnet1Subnet536B997A", 627 | }, 628 | { 629 | "Ref": "VpcPrivateSubnet2Subnet3788AAA1", 630 | }, 631 | ], 632 | }, 633 | }, 634 | "TaskDefinition": { 635 | "Ref": "ApiServiceTask878B1807", 636 | }, 637 | }, 638 | "Type": "AWS::ECS::Service", 639 | }, 640 | "ApiServiceFargateServiceSecurityGroupE31C96C6": { 641 | "DependsOn": [ 642 | "ApiServiceTaskTaskRoleDefaultPolicy982AD2DC", 643 | "ApiServiceTaskTaskRole06F87EBE", 644 | ], 645 | "Properties": { 646 | "GroupDescription": "TestStack/ApiService/FargateService/SecurityGroup", 647 | "SecurityGroupEgress": [ 648 | { 649 | "CidrIp": "0.0.0.0/0", 650 | "Description": "Allow all outbound traffic by default", 651 | "IpProtocol": "-1", 652 | }, 653 | ], 654 | "VpcId": { 655 | "Ref": "Vpc8378EB38", 656 | }, 657 | }, 658 | "Type": "AWS::EC2::SecurityGroup", 659 | }, 660 | "ApiServiceFargateServiceSecurityGroupfromTestStackAlbSecurityGroup8A47F1DC5001CFAD1D48": { 661 | "DependsOn": [ 662 | "ApiServiceTaskTaskRoleDefaultPolicy982AD2DC", 663 | "ApiServiceTaskTaskRole06F87EBE", 664 | ], 665 | "Properties": { 666 | "Description": "Load balancer to target", 667 | "FromPort": 5001, 668 | "GroupId": { 669 | "Fn::GetAtt": [ 670 | "ApiServiceFargateServiceSecurityGroupE31C96C6", 671 | "GroupId", 672 | ], 673 | }, 674 | "IpProtocol": "tcp", 675 | "SourceSecurityGroupId": { 676 | "Fn::GetAtt": [ 677 | "AlbSecurityGroup433229ED", 678 | "GroupId", 679 | ], 680 | }, 681 | "ToPort": 5001, 682 | }, 683 | "Type": "AWS::EC2::SecurityGroupIngress", 684 | }, 685 | "ApiServiceTask878B1807": { 686 | "Properties": { 687 | "ContainerDefinitions": [ 688 | { 689 | "Environment": [ 690 | { 691 | "Name": "MODE", 692 | "Value": "api", 693 | }, 694 | { 695 | "Name": "LOG_LEVEL", 696 | "Value": "ERROR", 697 | }, 698 | { 699 | "Name": "DEBUG", 700 | "Value": "false", 701 | }, 702 | { 703 | "Name": "CONSOLE_WEB_URL", 704 | "Value": "https://dify.example.com", 705 | }, 706 | { 707 | "Name": "CONSOLE_API_URL", 708 | "Value": "https://dify.example.com", 709 | }, 710 | { 711 | "Name": "SERVICE_API_URL", 712 | "Value": "https://dify.example.com", 713 | }, 714 | { 715 | "Name": "APP_WEB_URL", 716 | "Value": "https://dify.example.com", 717 | }, 718 | { 719 | "Name": "REDIS_HOST", 720 | "Value": { 721 | "Fn::GetAtt": [ 722 | "RedisFF642DF2", 723 | "PrimaryEndPoint.Address", 724 | ], 725 | }, 726 | }, 727 | { 728 | "Name": "REDIS_PORT", 729 | "Value": "6379", 730 | }, 731 | { 732 | "Name": "REDIS_USE_SSL", 733 | "Value": "true", 734 | }, 735 | { 736 | "Name": "REDIS_DB", 737 | "Value": "0", 738 | }, 739 | { 740 | "Name": "WEB_API_CORS_ALLOW_ORIGINS", 741 | "Value": "*", 742 | }, 743 | { 744 | "Name": "CONSOLE_CORS_ALLOW_ORIGINS", 745 | "Value": "*", 746 | }, 747 | { 748 | "Name": "STORAGE_TYPE", 749 | "Value": "s3", 750 | }, 751 | { 752 | "Name": "S3_BUCKET_NAME", 753 | "Value": { 754 | "Ref": "StorageBucket19DB2FF8", 755 | }, 756 | }, 757 | { 758 | "Name": "S3_REGION", 759 | "Value": { 760 | "Ref": "AWS::Region", 761 | }, 762 | }, 763 | { 764 | "Name": "DB_DATABASE", 765 | "Value": "main", 766 | }, 767 | { 768 | "Name": "VECTOR_STORE", 769 | "Value": "pgvector", 770 | }, 771 | { 772 | "Name": "PGVECTOR_DATABASE", 773 | "Value": "pgvector", 774 | }, 775 | { 776 | "Name": "CODE_EXECUTION_ENDPOINT", 777 | "Value": "http://localhost:8194", 778 | }, 779 | ], 780 | "Essential": true, 781 | "HealthCheck": { 782 | "Command": [ 783 | "CMD-SHELL", 784 | "curl -f http://localhost:5001/health || exit 1", 785 | ], 786 | "Interval": 15, 787 | "Retries": 5, 788 | "StartPeriod": 30, 789 | "Timeout": 5, 790 | }, 791 | "Image": "langgenius/dify-api:latest", 792 | "LogConfiguration": { 793 | "LogDriver": "awslogs", 794 | "Options": { 795 | "awslogs-group": { 796 | "Ref": "ApiServiceTaskMainLogGroup4A8BF33F", 797 | }, 798 | "awslogs-region": { 799 | "Ref": "AWS::Region", 800 | }, 801 | "awslogs-stream-prefix": "log", 802 | }, 803 | }, 804 | "Name": "Main", 805 | "PortMappings": [ 806 | { 807 | "ContainerPort": 5001, 808 | "Protocol": "tcp", 809 | }, 810 | ], 811 | "Secrets": [ 812 | { 813 | "Name": "DB_USERNAME", 814 | "ValueFrom": { 815 | "Fn::Join": [ 816 | "", 817 | [ 818 | { 819 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 820 | }, 821 | ":username::", 822 | ], 823 | ], 824 | }, 825 | }, 826 | { 827 | "Name": "DB_HOST", 828 | "ValueFrom": { 829 | "Fn::Join": [ 830 | "", 831 | [ 832 | { 833 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 834 | }, 835 | ":host::", 836 | ], 837 | ], 838 | }, 839 | }, 840 | { 841 | "Name": "DB_PORT", 842 | "ValueFrom": { 843 | "Fn::Join": [ 844 | "", 845 | [ 846 | { 847 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 848 | }, 849 | ":port::", 850 | ], 851 | ], 852 | }, 853 | }, 854 | { 855 | "Name": "DB_PASSWORD", 856 | "ValueFrom": { 857 | "Fn::Join": [ 858 | "", 859 | [ 860 | { 861 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 862 | }, 863 | ":password::", 864 | ], 865 | ], 866 | }, 867 | }, 868 | { 869 | "Name": "PGVECTOR_USER", 870 | "ValueFrom": { 871 | "Fn::Join": [ 872 | "", 873 | [ 874 | { 875 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 876 | }, 877 | ":username::", 878 | ], 879 | ], 880 | }, 881 | }, 882 | { 883 | "Name": "PGVECTOR_HOST", 884 | "ValueFrom": { 885 | "Fn::Join": [ 886 | "", 887 | [ 888 | { 889 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 890 | }, 891 | ":host::", 892 | ], 893 | ], 894 | }, 895 | }, 896 | { 897 | "Name": "PGVECTOR_PORT", 898 | "ValueFrom": { 899 | "Fn::Join": [ 900 | "", 901 | [ 902 | { 903 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 904 | }, 905 | ":port::", 906 | ], 907 | ], 908 | }, 909 | }, 910 | { 911 | "Name": "PGVECTOR_PASSWORD", 912 | "ValueFrom": { 913 | "Fn::Join": [ 914 | "", 915 | [ 916 | { 917 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 918 | }, 919 | ":password::", 920 | ], 921 | ], 922 | }, 923 | }, 924 | { 925 | "Name": "REDIS_PASSWORD", 926 | "ValueFrom": { 927 | "Ref": "RedisAuthToken9E34F6A5", 928 | }, 929 | }, 930 | { 931 | "Name": "CELERY_BROKER_URL", 932 | "ValueFrom": { 933 | "Fn::Join": [ 934 | "", 935 | [ 936 | "arn:", 937 | { 938 | "Ref": "AWS::Partition", 939 | }, 940 | ":ssm:", 941 | { 942 | "Ref": "AWS::Region", 943 | }, 944 | ":", 945 | { 946 | "Ref": "AWS::AccountId", 947 | }, 948 | ":parameter/", 949 | { 950 | "Ref": "RedisBrokerUrlA8582E06", 951 | }, 952 | ], 953 | ], 954 | }, 955 | }, 956 | { 957 | "Name": "SECRET_KEY", 958 | "ValueFrom": { 959 | "Ref": "ApiServiceEncryptionSecretF73F9ECD", 960 | }, 961 | }, 962 | { 963 | "Name": "CODE_EXECUTION_API_KEY", 964 | "ValueFrom": { 965 | "Ref": "ApiServiceEncryptionSecretF73F9ECD", 966 | }, 967 | }, 968 | ], 969 | }, 970 | { 971 | "Environment": [ 972 | { 973 | "Name": "GIN_MODE", 974 | "Value": "release", 975 | }, 976 | { 977 | "Name": "WORKER_TIMEOUT", 978 | "Value": "15", 979 | }, 980 | { 981 | "Name": "ENABLE_NETWORK", 982 | "Value": "true", 983 | }, 984 | { 985 | "Name": "ALLOWED_SYSCALLS", 986 | "Value": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456", 987 | }, 988 | { 989 | "Name": "PYTHON_LIB_PATH", 990 | "Value": "/usr/local/lib/python3.10,/usr/lib/python3.10,/usr/lib/python3,/usr/lib/x86_64-linux-gnu,/etc/ssl/certs/ca-certificates.crt,/etc/nsswitch.conf,/etc/hosts,/etc/resolv.conf,/run/systemd/resolve/stub-resolv.conf,/run/resolvconf/resolv.conf", 991 | }, 992 | ], 993 | "Essential": true, 994 | "Image": { 995 | "Fn::Sub": "\${AWS::AccountId}.dkr.ecr.\${AWS::Region}.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-\${AWS::AccountId}-\${AWS::Region}:4f82f88e853f5bb99f9a2c526dcacc44aea241f3f4b9f74e8d75d38ad33627f9", 996 | }, 997 | "LogConfiguration": { 998 | "LogDriver": "awslogs", 999 | "Options": { 1000 | "awslogs-group": { 1001 | "Ref": "ApiServiceTaskSandboxLogGroupDDF292A0", 1002 | }, 1003 | "awslogs-region": { 1004 | "Ref": "AWS::Region", 1005 | }, 1006 | "awslogs-stream-prefix": "log", 1007 | }, 1008 | }, 1009 | "Name": "Sandbox", 1010 | "PortMappings": [ 1011 | { 1012 | "ContainerPort": 8194, 1013 | "Protocol": "tcp", 1014 | }, 1015 | ], 1016 | "Secrets": [ 1017 | { 1018 | "Name": "API_KEY", 1019 | "ValueFrom": { 1020 | "Ref": "ApiServiceEncryptionSecretF73F9ECD", 1021 | }, 1022 | }, 1023 | ], 1024 | }, 1025 | ], 1026 | "Cpu": "1024", 1027 | "ExecutionRoleArn": { 1028 | "Fn::GetAtt": [ 1029 | "ApiServiceTaskExecutionRoleFE812553", 1030 | "Arn", 1031 | ], 1032 | }, 1033 | "Family": "TestStackApiServiceTaskBFA8CBF7", 1034 | "Memory": "2048", 1035 | "NetworkMode": "awsvpc", 1036 | "RequiresCompatibilities": [ 1037 | "FARGATE", 1038 | ], 1039 | "RuntimePlatform": { 1040 | "CpuArchitecture": "X86_64", 1041 | }, 1042 | "TaskRoleArn": { 1043 | "Fn::GetAtt": [ 1044 | "ApiServiceTaskTaskRole06F87EBE", 1045 | "Arn", 1046 | ], 1047 | }, 1048 | }, 1049 | "Type": "AWS::ECS::TaskDefinition", 1050 | }, 1051 | "ApiServiceTaskExecutionRoleDefaultPolicy38AB6296": { 1052 | "Properties": { 1053 | "PolicyDocument": { 1054 | "Statement": [ 1055 | { 1056 | "Action": [ 1057 | "logs:CreateLogStream", 1058 | "logs:PutLogEvents", 1059 | ], 1060 | "Effect": "Allow", 1061 | "Resource": { 1062 | "Fn::GetAtt": [ 1063 | "ApiServiceTaskMainLogGroup4A8BF33F", 1064 | "Arn", 1065 | ], 1066 | }, 1067 | }, 1068 | { 1069 | "Action": [ 1070 | "secretsmanager:GetSecretValue", 1071 | "secretsmanager:DescribeSecret", 1072 | ], 1073 | "Effect": "Allow", 1074 | "Resource": { 1075 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 1076 | }, 1077 | }, 1078 | { 1079 | "Action": [ 1080 | "secretsmanager:GetSecretValue", 1081 | "secretsmanager:DescribeSecret", 1082 | ], 1083 | "Effect": "Allow", 1084 | "Resource": { 1085 | "Ref": "RedisAuthToken9E34F6A5", 1086 | }, 1087 | }, 1088 | { 1089 | "Action": [ 1090 | "ssm:DescribeParameters", 1091 | "ssm:GetParameters", 1092 | "ssm:GetParameter", 1093 | "ssm:GetParameterHistory", 1094 | ], 1095 | "Effect": "Allow", 1096 | "Resource": { 1097 | "Fn::Join": [ 1098 | "", 1099 | [ 1100 | "arn:", 1101 | { 1102 | "Ref": "AWS::Partition", 1103 | }, 1104 | ":ssm:", 1105 | { 1106 | "Ref": "AWS::Region", 1107 | }, 1108 | ":", 1109 | { 1110 | "Ref": "AWS::AccountId", 1111 | }, 1112 | ":parameter/", 1113 | { 1114 | "Ref": "RedisBrokerUrlA8582E06", 1115 | }, 1116 | ], 1117 | ], 1118 | }, 1119 | }, 1120 | { 1121 | "Action": [ 1122 | "secretsmanager:GetSecretValue", 1123 | "secretsmanager:DescribeSecret", 1124 | ], 1125 | "Effect": "Allow", 1126 | "Resource": { 1127 | "Ref": "ApiServiceEncryptionSecretF73F9ECD", 1128 | }, 1129 | }, 1130 | { 1131 | "Action": [ 1132 | "ecr:BatchCheckLayerAvailability", 1133 | "ecr:GetDownloadUrlForLayer", 1134 | "ecr:BatchGetImage", 1135 | ], 1136 | "Effect": "Allow", 1137 | "Resource": { 1138 | "Fn::Join": [ 1139 | "", 1140 | [ 1141 | "arn:", 1142 | { 1143 | "Ref": "AWS::Partition", 1144 | }, 1145 | ":ecr:", 1146 | { 1147 | "Ref": "AWS::Region", 1148 | }, 1149 | ":", 1150 | { 1151 | "Ref": "AWS::AccountId", 1152 | }, 1153 | ":repository/", 1154 | { 1155 | "Fn::Sub": "cdk-hnb659fds-container-assets-\${AWS::AccountId}-\${AWS::Region}", 1156 | }, 1157 | ], 1158 | ], 1159 | }, 1160 | }, 1161 | { 1162 | "Action": "ecr:GetAuthorizationToken", 1163 | "Effect": "Allow", 1164 | "Resource": "*", 1165 | }, 1166 | { 1167 | "Action": [ 1168 | "logs:CreateLogStream", 1169 | "logs:PutLogEvents", 1170 | ], 1171 | "Effect": "Allow", 1172 | "Resource": { 1173 | "Fn::GetAtt": [ 1174 | "ApiServiceTaskSandboxLogGroupDDF292A0", 1175 | "Arn", 1176 | ], 1177 | }, 1178 | }, 1179 | ], 1180 | "Version": "2012-10-17", 1181 | }, 1182 | "PolicyName": "ApiServiceTaskExecutionRoleDefaultPolicy38AB6296", 1183 | "Roles": [ 1184 | { 1185 | "Ref": "ApiServiceTaskExecutionRoleFE812553", 1186 | }, 1187 | ], 1188 | }, 1189 | "Type": "AWS::IAM::Policy", 1190 | }, 1191 | "ApiServiceTaskExecutionRoleFE812553": { 1192 | "Properties": { 1193 | "AssumeRolePolicyDocument": { 1194 | "Statement": [ 1195 | { 1196 | "Action": "sts:AssumeRole", 1197 | "Effect": "Allow", 1198 | "Principal": { 1199 | "Service": "ecs-tasks.amazonaws.com", 1200 | }, 1201 | }, 1202 | ], 1203 | "Version": "2012-10-17", 1204 | }, 1205 | }, 1206 | "Type": "AWS::IAM::Role", 1207 | }, 1208 | "ApiServiceTaskMainLogGroup4A8BF33F": { 1209 | "DeletionPolicy": "Retain", 1210 | "Type": "AWS::Logs::LogGroup", 1211 | "UpdateReplacePolicy": "Retain", 1212 | }, 1213 | "ApiServiceTaskSandboxLogGroupDDF292A0": { 1214 | "DeletionPolicy": "Retain", 1215 | "Type": "AWS::Logs::LogGroup", 1216 | "UpdateReplacePolicy": "Retain", 1217 | }, 1218 | "ApiServiceTaskTaskRole06F87EBE": { 1219 | "Properties": { 1220 | "AssumeRolePolicyDocument": { 1221 | "Statement": [ 1222 | { 1223 | "Action": "sts:AssumeRole", 1224 | "Effect": "Allow", 1225 | "Principal": { 1226 | "Service": "ecs-tasks.amazonaws.com", 1227 | }, 1228 | }, 1229 | ], 1230 | "Version": "2012-10-17", 1231 | }, 1232 | }, 1233 | "Type": "AWS::IAM::Role", 1234 | }, 1235 | "ApiServiceTaskTaskRoleDefaultPolicy982AD2DC": { 1236 | "Properties": { 1237 | "PolicyDocument": { 1238 | "Statement": [ 1239 | { 1240 | "Action": [ 1241 | "s3:GetObject*", 1242 | "s3:GetBucket*", 1243 | "s3:List*", 1244 | "s3:DeleteObject*", 1245 | "s3:PutObject", 1246 | "s3:PutObjectLegalHold", 1247 | "s3:PutObjectRetention", 1248 | "s3:PutObjectTagging", 1249 | "s3:PutObjectVersionTagging", 1250 | "s3:Abort*", 1251 | ], 1252 | "Effect": "Allow", 1253 | "Resource": [ 1254 | { 1255 | "Fn::GetAtt": [ 1256 | "StorageBucket19DB2FF8", 1257 | "Arn", 1258 | ], 1259 | }, 1260 | { 1261 | "Fn::Join": [ 1262 | "", 1263 | [ 1264 | { 1265 | "Fn::GetAtt": [ 1266 | "StorageBucket19DB2FF8", 1267 | "Arn", 1268 | ], 1269 | }, 1270 | "/*", 1271 | ], 1272 | ], 1273 | }, 1274 | ], 1275 | }, 1276 | { 1277 | "Action": [ 1278 | "bedrock:InvokeModel", 1279 | "bedrock:InvokeModelWithResponseStream", 1280 | ], 1281 | "Effect": "Allow", 1282 | "Resource": "*", 1283 | }, 1284 | { 1285 | "Action": [ 1286 | "ssmmessages:CreateControlChannel", 1287 | "ssmmessages:CreateDataChannel", 1288 | "ssmmessages:OpenControlChannel", 1289 | "ssmmessages:OpenDataChannel", 1290 | ], 1291 | "Effect": "Allow", 1292 | "Resource": "*", 1293 | }, 1294 | { 1295 | "Action": "logs:DescribeLogGroups", 1296 | "Effect": "Allow", 1297 | "Resource": "*", 1298 | }, 1299 | { 1300 | "Action": [ 1301 | "logs:CreateLogStream", 1302 | "logs:DescribeLogStreams", 1303 | "logs:PutLogEvents", 1304 | ], 1305 | "Effect": "Allow", 1306 | "Resource": "*", 1307 | }, 1308 | ], 1309 | "Version": "2012-10-17", 1310 | }, 1311 | "PolicyName": "ApiServiceTaskTaskRoleDefaultPolicy982AD2DC", 1312 | "Roles": [ 1313 | { 1314 | "Ref": "ApiServiceTaskTaskRole06F87EBE", 1315 | }, 1316 | ], 1317 | }, 1318 | "Type": "AWS::IAM::Policy", 1319 | }, 1320 | "ClusterEB0386A7": { 1321 | "Properties": { 1322 | "ClusterSettings": [ 1323 | { 1324 | "Name": "containerInsights", 1325 | "Value": "enabled", 1326 | }, 1327 | ], 1328 | }, 1329 | "Type": "AWS::ECS::Cluster", 1330 | }, 1331 | "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { 1332 | "DependsOn": [ 1333 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", 1334 | ], 1335 | "Properties": { 1336 | "Code": { 1337 | "S3Bucket": { 1338 | "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", 1339 | }, 1340 | "S3Key": "faa95a81ae7d7373f3e1f242268f904eb748d8d0fdd306e8a6fe515a1905a7d6.zip", 1341 | }, 1342 | "Description": { 1343 | "Fn::Join": [ 1344 | "", 1345 | [ 1346 | "Lambda function for auto-deleting objects in ", 1347 | { 1348 | "Ref": "StorageBucket19DB2FF8", 1349 | }, 1350 | " S3 bucket.", 1351 | ], 1352 | ], 1353 | }, 1354 | "Handler": "index.handler", 1355 | "MemorySize": 128, 1356 | "Role": { 1357 | "Fn::GetAtt": [ 1358 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", 1359 | "Arn", 1360 | ], 1361 | }, 1362 | "Runtime": { 1363 | "Fn::FindInMap": [ 1364 | "LatestNodeRuntimeMap", 1365 | { 1366 | "Ref": "AWS::Region", 1367 | }, 1368 | "value", 1369 | ], 1370 | }, 1371 | "Timeout": 900, 1372 | }, 1373 | "Type": "AWS::Lambda::Function", 1374 | }, 1375 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { 1376 | "Properties": { 1377 | "AssumeRolePolicyDocument": { 1378 | "Statement": [ 1379 | { 1380 | "Action": "sts:AssumeRole", 1381 | "Effect": "Allow", 1382 | "Principal": { 1383 | "Service": "lambda.amazonaws.com", 1384 | }, 1385 | }, 1386 | ], 1387 | "Version": "2012-10-17", 1388 | }, 1389 | "ManagedPolicyArns": [ 1390 | { 1391 | "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 1392 | }, 1393 | ], 1394 | }, 1395 | "Type": "AWS::IAM::Role", 1396 | }, 1397 | "PostgresCluster53E5BDAB": { 1398 | "DeletionPolicy": "Delete", 1399 | "Properties": { 1400 | "CopyTagsToSnapshot": true, 1401 | "DBClusterParameterGroupName": "default.aurora-postgresql15", 1402 | "DBSubnetGroupName": { 1403 | "Ref": "PostgresClusterSubnets99BD7A61", 1404 | }, 1405 | "DatabaseName": "main", 1406 | "EnableHttpEndpoint": true, 1407 | "Engine": "aurora-postgresql", 1408 | "EngineVersion": "15.5", 1409 | "MasterUserPassword": { 1410 | "Fn::Join": [ 1411 | "", 1412 | [ 1413 | "{{resolve:secretsmanager:", 1414 | { 1415 | "Ref": "PostgresClusterSecretC5EAFDEC", 1416 | }, 1417 | ":SecretString:password::}}", 1418 | ], 1419 | ], 1420 | }, 1421 | "MasterUsername": { 1422 | "Fn::Join": [ 1423 | "", 1424 | [ 1425 | "{{resolve:secretsmanager:", 1426 | { 1427 | "Ref": "PostgresClusterSecretC5EAFDEC", 1428 | }, 1429 | ":SecretString:username::}}", 1430 | ], 1431 | ], 1432 | }, 1433 | "Port": 5432, 1434 | "ServerlessV2ScalingConfiguration": { 1435 | "MaxCapacity": 2, 1436 | "MinCapacity": 0.5, 1437 | }, 1438 | "StorageEncrypted": true, 1439 | "VpcSecurityGroupIds": [ 1440 | { 1441 | "Fn::GetAtt": [ 1442 | "PostgresClusterSecurityGroup08DE6EE8", 1443 | "GroupId", 1444 | ], 1445 | }, 1446 | ], 1447 | }, 1448 | "Type": "AWS::RDS::DBCluster", 1449 | "UpdateReplacePolicy": "Delete", 1450 | }, 1451 | "PostgresClusterSecretAttachment8DDCF2A8": { 1452 | "Properties": { 1453 | "SecretId": { 1454 | "Ref": "PostgresClusterSecretC5EAFDEC", 1455 | }, 1456 | "TargetId": { 1457 | "Ref": "PostgresCluster53E5BDAB", 1458 | }, 1459 | "TargetType": "AWS::RDS::DBCluster", 1460 | }, 1461 | "Type": "AWS::SecretsManager::SecretTargetAttachment", 1462 | }, 1463 | "PostgresClusterSecretC5EAFDEC": { 1464 | "DeletionPolicy": "Delete", 1465 | "Properties": { 1466 | "Description": { 1467 | "Fn::Join": [ 1468 | "", 1469 | [ 1470 | "Generated by the CDK for stack: ", 1471 | { 1472 | "Ref": "AWS::StackName", 1473 | }, 1474 | ], 1475 | ], 1476 | }, 1477 | "GenerateSecretString": { 1478 | "ExcludeCharacters": " %+~\`#$&*()|[]{}:;<>?!'/@"\\", 1479 | "GenerateStringKey": "password", 1480 | "PasswordLength": 30, 1481 | "SecretStringTemplate": "{"username":"postgres"}", 1482 | }, 1483 | }, 1484 | "Type": "AWS::SecretsManager::Secret", 1485 | "UpdateReplacePolicy": "Delete", 1486 | }, 1487 | "PostgresClusterSecurityGroup08DE6EE8": { 1488 | "Properties": { 1489 | "GroupDescription": "RDS security group", 1490 | "SecurityGroupEgress": [ 1491 | { 1492 | "CidrIp": "0.0.0.0/0", 1493 | "Description": "Allow all outbound traffic by default", 1494 | "IpProtocol": "-1", 1495 | }, 1496 | ], 1497 | "VpcId": { 1498 | "Ref": "Vpc8378EB38", 1499 | }, 1500 | }, 1501 | "Type": "AWS::EC2::SecurityGroup", 1502 | }, 1503 | "PostgresClusterSecurityGroupfromTestStackApiServiceFargateServiceSecurityGroup7DD0AF44IndirectPort6FBE377A": { 1504 | "Properties": { 1505 | "Description": "from TestStackApiServiceFargateServiceSecurityGroup7DD0AF44:{IndirectPort}", 1506 | "FromPort": { 1507 | "Fn::GetAtt": [ 1508 | "PostgresCluster53E5BDAB", 1509 | "Endpoint.Port", 1510 | ], 1511 | }, 1512 | "GroupId": { 1513 | "Fn::GetAtt": [ 1514 | "PostgresClusterSecurityGroup08DE6EE8", 1515 | "GroupId", 1516 | ], 1517 | }, 1518 | "IpProtocol": "tcp", 1519 | "SourceSecurityGroupId": { 1520 | "Fn::GetAtt": [ 1521 | "ApiServiceFargateServiceSecurityGroupE31C96C6", 1522 | "GroupId", 1523 | ], 1524 | }, 1525 | "ToPort": { 1526 | "Fn::GetAtt": [ 1527 | "PostgresCluster53E5BDAB", 1528 | "Endpoint.Port", 1529 | ], 1530 | }, 1531 | }, 1532 | "Type": "AWS::EC2::SecurityGroupIngress", 1533 | }, 1534 | "PostgresClusterSecurityGroupfromTestStackWorkerServiceFargateServiceSecurityGroup3259E932IndirectPort6C372135": { 1535 | "Properties": { 1536 | "Description": "from TestStackWorkerServiceFargateServiceSecurityGroup3259E932:{IndirectPort}", 1537 | "FromPort": { 1538 | "Fn::GetAtt": [ 1539 | "PostgresCluster53E5BDAB", 1540 | "Endpoint.Port", 1541 | ], 1542 | }, 1543 | "GroupId": { 1544 | "Fn::GetAtt": [ 1545 | "PostgresClusterSecurityGroup08DE6EE8", 1546 | "GroupId", 1547 | ], 1548 | }, 1549 | "IpProtocol": "tcp", 1550 | "SourceSecurityGroupId": { 1551 | "Fn::GetAtt": [ 1552 | "WorkerServiceFargateServiceSecurityGroupEC8152E8", 1553 | "GroupId", 1554 | ], 1555 | }, 1556 | "ToPort": { 1557 | "Fn::GetAtt": [ 1558 | "PostgresCluster53E5BDAB", 1559 | "Endpoint.Port", 1560 | ], 1561 | }, 1562 | }, 1563 | "Type": "AWS::EC2::SecurityGroupIngress", 1564 | }, 1565 | "PostgresClusterSubnets99BD7A61": { 1566 | "Properties": { 1567 | "DBSubnetGroupDescription": "Subnets for Cluster database", 1568 | "SubnetIds": [ 1569 | { 1570 | "Ref": "VpcPrivateSubnet1Subnet536B997A", 1571 | }, 1572 | { 1573 | "Ref": "VpcPrivateSubnet2Subnet3788AAA1", 1574 | }, 1575 | ], 1576 | }, 1577 | "Type": "AWS::RDS::DBSubnetGroup", 1578 | }, 1579 | "PostgresClusterWriterF88DD8CC": { 1580 | "DeletionPolicy": "Delete", 1581 | "DependsOn": [ 1582 | "VpcPrivateSubnet1DefaultRouteBE02A9ED", 1583 | "VpcPrivateSubnet1RouteTableAssociation70C59FA6", 1584 | "VpcPrivateSubnet2DefaultRoute060D2087", 1585 | "VpcPrivateSubnet2RouteTableAssociationA89CAD56", 1586 | ], 1587 | "Properties": { 1588 | "AutoMinorVersionUpgrade": true, 1589 | "DBClusterIdentifier": { 1590 | "Ref": "PostgresCluster53E5BDAB", 1591 | }, 1592 | "DBInstanceClass": "db.serverless", 1593 | "Engine": "aurora-postgresql", 1594 | "PromotionTier": 0, 1595 | "PubliclyAccessible": false, 1596 | }, 1597 | "Type": "AWS::RDS::DBInstance", 1598 | "UpdateReplacePolicy": "Delete", 1599 | }, 1600 | "PostgresQuery0CustomResourcePolicy41175230": { 1601 | "Properties": { 1602 | "PolicyDocument": { 1603 | "Statement": [ 1604 | { 1605 | "Action": "rds-data:ExecuteStatement", 1606 | "Effect": "Allow", 1607 | "Resource": { 1608 | "Fn::Join": [ 1609 | "", 1610 | [ 1611 | "arn:", 1612 | { 1613 | "Ref": "AWS::Partition", 1614 | }, 1615 | ":rds:", 1616 | { 1617 | "Ref": "AWS::Region", 1618 | }, 1619 | ":", 1620 | { 1621 | "Ref": "AWS::AccountId", 1622 | }, 1623 | ":cluster:", 1624 | { 1625 | "Ref": "PostgresCluster53E5BDAB", 1626 | }, 1627 | ], 1628 | ], 1629 | }, 1630 | }, 1631 | ], 1632 | "Version": "2012-10-17", 1633 | }, 1634 | "PolicyName": "PostgresQuery0CustomResourcePolicy41175230", 1635 | "Roles": [ 1636 | { 1637 | "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", 1638 | }, 1639 | ], 1640 | }, 1641 | "Type": "AWS::IAM::Policy", 1642 | }, 1643 | "PostgresQuery0FD53F567": { 1644 | "DeletionPolicy": "Delete", 1645 | "DependsOn": [ 1646 | "PostgresQuery0CustomResourcePolicy41175230", 1647 | "PostgresWaitForHttpEndpointReady6D04AFAC", 1648 | ], 1649 | "Properties": { 1650 | "Create": { 1651 | "Fn::Join": [ 1652 | "", 1653 | [ 1654 | "{"service":"rds-data","action":"ExecuteStatement","parameters":{"resourceArn":"arn:", 1655 | { 1656 | "Ref": "AWS::Partition", 1657 | }, 1658 | ":rds:", 1659 | { 1660 | "Ref": "AWS::Region", 1661 | }, 1662 | ":", 1663 | { 1664 | "Ref": "AWS::AccountId", 1665 | }, 1666 | ":cluster:", 1667 | { 1668 | "Ref": "PostgresCluster53E5BDAB", 1669 | }, 1670 | "","secretArn":"", 1671 | { 1672 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 1673 | }, 1674 | "","sql":"CREATE DATABASE pgvector;"},"physicalResourceId":{"id":"arn:", 1675 | { 1676 | "Ref": "AWS::Partition", 1677 | }, 1678 | ":rds:", 1679 | { 1680 | "Ref": "AWS::Region", 1681 | }, 1682 | ":", 1683 | { 1684 | "Ref": "AWS::AccountId", 1685 | }, 1686 | ":cluster:", 1687 | { 1688 | "Ref": "PostgresCluster53E5BDAB", 1689 | }, 1690 | ""}}", 1691 | ], 1692 | ], 1693 | }, 1694 | "InstallLatestAwsSdk": true, 1695 | "ServiceToken": { 1696 | "Fn::GetAtt": [ 1697 | "AWS679f53fac002430cb0da5b7982bd22872D164C4C", 1698 | "Arn", 1699 | ], 1700 | }, 1701 | "Update": { 1702 | "Fn::Join": [ 1703 | "", 1704 | [ 1705 | "{"service":"rds-data","action":"ExecuteStatement","parameters":{"resourceArn":"arn:", 1706 | { 1707 | "Ref": "AWS::Partition", 1708 | }, 1709 | ":rds:", 1710 | { 1711 | "Ref": "AWS::Region", 1712 | }, 1713 | ":", 1714 | { 1715 | "Ref": "AWS::AccountId", 1716 | }, 1717 | ":cluster:", 1718 | { 1719 | "Ref": "PostgresCluster53E5BDAB", 1720 | }, 1721 | "","secretArn":"", 1722 | { 1723 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 1724 | }, 1725 | "","sql":"CREATE DATABASE pgvector;"},"physicalResourceId":{"id":"arn:", 1726 | { 1727 | "Ref": "AWS::Partition", 1728 | }, 1729 | ":rds:", 1730 | { 1731 | "Ref": "AWS::Region", 1732 | }, 1733 | ":", 1734 | { 1735 | "Ref": "AWS::AccountId", 1736 | }, 1737 | ":cluster:", 1738 | { 1739 | "Ref": "PostgresCluster53E5BDAB", 1740 | }, 1741 | ""}}", 1742 | ], 1743 | ], 1744 | }, 1745 | }, 1746 | "Type": "Custom::AWS", 1747 | "UpdateReplacePolicy": "Delete", 1748 | }, 1749 | "PostgresQuery1B175C979": { 1750 | "DeletionPolicy": "Delete", 1751 | "DependsOn": [ 1752 | "PostgresQuery0FD53F567", 1753 | "PostgresQuery1CustomResourcePolicy1E34BE89", 1754 | ], 1755 | "Properties": { 1756 | "Create": { 1757 | "Fn::Join": [ 1758 | "", 1759 | [ 1760 | "{"service":"rds-data","action":"ExecuteStatement","parameters":{"resourceArn":"arn:", 1761 | { 1762 | "Ref": "AWS::Partition", 1763 | }, 1764 | ":rds:", 1765 | { 1766 | "Ref": "AWS::Region", 1767 | }, 1768 | ":", 1769 | { 1770 | "Ref": "AWS::AccountId", 1771 | }, 1772 | ":cluster:", 1773 | { 1774 | "Ref": "PostgresCluster53E5BDAB", 1775 | }, 1776 | "","secretArn":"", 1777 | { 1778 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 1779 | }, 1780 | "","database":"pgvector","sql":"CREATE EXTENSION IF NOT EXISTS vector;"},"physicalResourceId":{"id":"arn:", 1781 | { 1782 | "Ref": "AWS::Partition", 1783 | }, 1784 | ":rds:", 1785 | { 1786 | "Ref": "AWS::Region", 1787 | }, 1788 | ":", 1789 | { 1790 | "Ref": "AWS::AccountId", 1791 | }, 1792 | ":cluster:", 1793 | { 1794 | "Ref": "PostgresCluster53E5BDAB", 1795 | }, 1796 | ""}}", 1797 | ], 1798 | ], 1799 | }, 1800 | "InstallLatestAwsSdk": true, 1801 | "ServiceToken": { 1802 | "Fn::GetAtt": [ 1803 | "AWS679f53fac002430cb0da5b7982bd22872D164C4C", 1804 | "Arn", 1805 | ], 1806 | }, 1807 | "Update": { 1808 | "Fn::Join": [ 1809 | "", 1810 | [ 1811 | "{"service":"rds-data","action":"ExecuteStatement","parameters":{"resourceArn":"arn:", 1812 | { 1813 | "Ref": "AWS::Partition", 1814 | }, 1815 | ":rds:", 1816 | { 1817 | "Ref": "AWS::Region", 1818 | }, 1819 | ":", 1820 | { 1821 | "Ref": "AWS::AccountId", 1822 | }, 1823 | ":cluster:", 1824 | { 1825 | "Ref": "PostgresCluster53E5BDAB", 1826 | }, 1827 | "","secretArn":"", 1828 | { 1829 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 1830 | }, 1831 | "","database":"pgvector","sql":"CREATE EXTENSION IF NOT EXISTS vector;"},"physicalResourceId":{"id":"arn:", 1832 | { 1833 | "Ref": "AWS::Partition", 1834 | }, 1835 | ":rds:", 1836 | { 1837 | "Ref": "AWS::Region", 1838 | }, 1839 | ":", 1840 | { 1841 | "Ref": "AWS::AccountId", 1842 | }, 1843 | ":cluster:", 1844 | { 1845 | "Ref": "PostgresCluster53E5BDAB", 1846 | }, 1847 | ""}}", 1848 | ], 1849 | ], 1850 | }, 1851 | }, 1852 | "Type": "Custom::AWS", 1853 | "UpdateReplacePolicy": "Delete", 1854 | }, 1855 | "PostgresQuery1CustomResourcePolicy1E34BE89": { 1856 | "Properties": { 1857 | "PolicyDocument": { 1858 | "Statement": [ 1859 | { 1860 | "Action": "rds-data:ExecuteStatement", 1861 | "Effect": "Allow", 1862 | "Resource": { 1863 | "Fn::Join": [ 1864 | "", 1865 | [ 1866 | "arn:", 1867 | { 1868 | "Ref": "AWS::Partition", 1869 | }, 1870 | ":rds:", 1871 | { 1872 | "Ref": "AWS::Region", 1873 | }, 1874 | ":", 1875 | { 1876 | "Ref": "AWS::AccountId", 1877 | }, 1878 | ":cluster:", 1879 | { 1880 | "Ref": "PostgresCluster53E5BDAB", 1881 | }, 1882 | ], 1883 | ], 1884 | }, 1885 | }, 1886 | ], 1887 | "Version": "2012-10-17", 1888 | }, 1889 | "PolicyName": "PostgresQuery1CustomResourcePolicy1E34BE89", 1890 | "Roles": [ 1891 | { 1892 | "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", 1893 | }, 1894 | ], 1895 | }, 1896 | "Type": "AWS::IAM::Policy", 1897 | }, 1898 | "PostgresWaitForHttpEndpointReady6D04AFAC": { 1899 | "DeletionPolicy": "Delete", 1900 | "DependsOn": [ 1901 | "PostgresClusterWriterF88DD8CC", 1902 | ], 1903 | "Properties": { 1904 | "ServiceToken": { 1905 | "Fn::GetAtt": [ 1906 | "TimeSleepCustomResourceHandler494c1b460d2f4e3b9bfd0b2cf10162f981117264", 1907 | "Arn", 1908 | ], 1909 | }, 1910 | "createDurationSeconds": 60, 1911 | "destroyDurationSeconds": 0, 1912 | }, 1913 | "Type": "Custom::TimeSleep", 1914 | "UpdateReplacePolicy": "Delete", 1915 | }, 1916 | "RedisAuthToken9E34F6A5": { 1917 | "DeletionPolicy": "Delete", 1918 | "Properties": { 1919 | "GenerateSecretString": { 1920 | "ExcludePunctuation": true, 1921 | "PasswordLength": 30, 1922 | }, 1923 | }, 1924 | "Type": "AWS::SecretsManager::Secret", 1925 | "UpdateReplacePolicy": "Delete", 1926 | }, 1927 | "RedisBrokerUrlA8582E06": { 1928 | "Properties": { 1929 | "Type": "String", 1930 | "Value": { 1931 | "Fn::Join": [ 1932 | "", 1933 | [ 1934 | "rediss://:{{resolve:secretsmanager:", 1935 | { 1936 | "Ref": "RedisAuthToken9E34F6A5", 1937 | }, 1938 | ":SecretString:::}}@", 1939 | { 1940 | "Fn::GetAtt": [ 1941 | "RedisFF642DF2", 1942 | "PrimaryEndPoint.Address", 1943 | ], 1944 | }, 1945 | ":6379/1", 1946 | ], 1947 | ], 1948 | }, 1949 | }, 1950 | "Type": "AWS::SSM::Parameter", 1951 | }, 1952 | "RedisFF642DF2": { 1953 | "Properties": { 1954 | "AtRestEncryptionEnabled": true, 1955 | "AuthToken": { 1956 | "Fn::Join": [ 1957 | "", 1958 | [ 1959 | "{{resolve:secretsmanager:", 1960 | { 1961 | "Ref": "RedisAuthToken9E34F6A5", 1962 | }, 1963 | ":SecretString:::}}", 1964 | ], 1965 | ], 1966 | }, 1967 | "AutomaticFailoverEnabled": true, 1968 | "CacheNodeType": "cache.t4g.micro", 1969 | "CacheSubnetGroupName": { 1970 | "Ref": "RedisSubnetGroup2387CBFF", 1971 | }, 1972 | "Engine": "Redis", 1973 | "EngineVersion": "7.1", 1974 | "MultiAZEnabled": true, 1975 | "NumNodeGroups": 1, 1976 | "Port": 6379, 1977 | "ReplicasPerNodeGroup": 1, 1978 | "ReplicationGroupDescription": "Dify redis cluster", 1979 | "SecurityGroupIds": [ 1980 | { 1981 | "Fn::GetAtt": [ 1982 | "RedisSecurityGroupC1E9FD21", 1983 | "GroupId", 1984 | ], 1985 | }, 1986 | ], 1987 | "TransitEncryptionEnabled": true, 1988 | }, 1989 | "Type": "AWS::ElastiCache::ReplicationGroup", 1990 | }, 1991 | "RedisSecurityGroupC1E9FD21": { 1992 | "Properties": { 1993 | "GroupDescription": "TestStack/Redis/SecurityGroup", 1994 | "SecurityGroupEgress": [ 1995 | { 1996 | "CidrIp": "0.0.0.0/0", 1997 | "Description": "Allow all outbound traffic by default", 1998 | "IpProtocol": "-1", 1999 | }, 2000 | ], 2001 | "VpcId": { 2002 | "Ref": "Vpc8378EB38", 2003 | }, 2004 | }, 2005 | "Type": "AWS::EC2::SecurityGroup", 2006 | }, 2007 | "RedisSecurityGroupfromTestStackApiServiceFargateServiceSecurityGroup7DD0AF446379F9E0F455": { 2008 | "Properties": { 2009 | "Description": "from TestStackApiServiceFargateServiceSecurityGroup7DD0AF44:6379", 2010 | "FromPort": 6379, 2011 | "GroupId": { 2012 | "Fn::GetAtt": [ 2013 | "RedisSecurityGroupC1E9FD21", 2014 | "GroupId", 2015 | ], 2016 | }, 2017 | "IpProtocol": "tcp", 2018 | "SourceSecurityGroupId": { 2019 | "Fn::GetAtt": [ 2020 | "ApiServiceFargateServiceSecurityGroupE31C96C6", 2021 | "GroupId", 2022 | ], 2023 | }, 2024 | "ToPort": 6379, 2025 | }, 2026 | "Type": "AWS::EC2::SecurityGroupIngress", 2027 | }, 2028 | "RedisSecurityGroupfromTestStackWorkerServiceFargateServiceSecurityGroup3259E93263795D7BE143": { 2029 | "Properties": { 2030 | "Description": "from TestStackWorkerServiceFargateServiceSecurityGroup3259E932:6379", 2031 | "FromPort": 6379, 2032 | "GroupId": { 2033 | "Fn::GetAtt": [ 2034 | "RedisSecurityGroupC1E9FD21", 2035 | "GroupId", 2036 | ], 2037 | }, 2038 | "IpProtocol": "tcp", 2039 | "SourceSecurityGroupId": { 2040 | "Fn::GetAtt": [ 2041 | "WorkerServiceFargateServiceSecurityGroupEC8152E8", 2042 | "GroupId", 2043 | ], 2044 | }, 2045 | "ToPort": 6379, 2046 | }, 2047 | "Type": "AWS::EC2::SecurityGroupIngress", 2048 | }, 2049 | "RedisSubnetGroup2387CBFF": { 2050 | "Properties": { 2051 | "Description": "private subnet", 2052 | "SubnetIds": [ 2053 | { 2054 | "Ref": "VpcPrivateSubnet1Subnet536B997A", 2055 | }, 2056 | { 2057 | "Ref": "VpcPrivateSubnet2Subnet3788AAA1", 2058 | }, 2059 | ], 2060 | }, 2061 | "Type": "AWS::ElastiCache::SubnetGroup", 2062 | }, 2063 | "StorageBucket19DB2FF8": { 2064 | "DeletionPolicy": "Delete", 2065 | "Properties": { 2066 | "PublicAccessBlockConfiguration": { 2067 | "BlockPublicAcls": true, 2068 | "BlockPublicPolicy": true, 2069 | "IgnorePublicAcls": true, 2070 | "RestrictPublicBuckets": true, 2071 | }, 2072 | "Tags": [ 2073 | { 2074 | "Key": "aws-cdk:auto-delete-objects", 2075 | "Value": "true", 2076 | }, 2077 | ], 2078 | }, 2079 | "Type": "AWS::S3::Bucket", 2080 | "UpdateReplacePolicy": "Delete", 2081 | }, 2082 | "StorageBucketAutoDeleteObjectsCustomResource105C81BD": { 2083 | "DeletionPolicy": "Delete", 2084 | "DependsOn": [ 2085 | "StorageBucketPolicy41A048DB", 2086 | ], 2087 | "Properties": { 2088 | "BucketName": { 2089 | "Ref": "StorageBucket19DB2FF8", 2090 | }, 2091 | "ServiceToken": { 2092 | "Fn::GetAtt": [ 2093 | "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", 2094 | "Arn", 2095 | ], 2096 | }, 2097 | }, 2098 | "Type": "Custom::S3AutoDeleteObjects", 2099 | "UpdateReplacePolicy": "Delete", 2100 | }, 2101 | "StorageBucketPolicy41A048DB": { 2102 | "Properties": { 2103 | "Bucket": { 2104 | "Ref": "StorageBucket19DB2FF8", 2105 | }, 2106 | "PolicyDocument": { 2107 | "Statement": [ 2108 | { 2109 | "Action": "s3:*", 2110 | "Condition": { 2111 | "Bool": { 2112 | "aws:SecureTransport": "false", 2113 | }, 2114 | }, 2115 | "Effect": "Deny", 2116 | "Principal": { 2117 | "AWS": "*", 2118 | }, 2119 | "Resource": [ 2120 | { 2121 | "Fn::GetAtt": [ 2122 | "StorageBucket19DB2FF8", 2123 | "Arn", 2124 | ], 2125 | }, 2126 | { 2127 | "Fn::Join": [ 2128 | "", 2129 | [ 2130 | { 2131 | "Fn::GetAtt": [ 2132 | "StorageBucket19DB2FF8", 2133 | "Arn", 2134 | ], 2135 | }, 2136 | "/*", 2137 | ], 2138 | ], 2139 | }, 2140 | ], 2141 | }, 2142 | { 2143 | "Action": [ 2144 | "s3:PutBucketPolicy", 2145 | "s3:GetBucket*", 2146 | "s3:List*", 2147 | "s3:DeleteObject*", 2148 | ], 2149 | "Effect": "Allow", 2150 | "Principal": { 2151 | "AWS": { 2152 | "Fn::GetAtt": [ 2153 | "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", 2154 | "Arn", 2155 | ], 2156 | }, 2157 | }, 2158 | "Resource": [ 2159 | { 2160 | "Fn::GetAtt": [ 2161 | "StorageBucket19DB2FF8", 2162 | "Arn", 2163 | ], 2164 | }, 2165 | { 2166 | "Fn::Join": [ 2167 | "", 2168 | [ 2169 | { 2170 | "Fn::GetAtt": [ 2171 | "StorageBucket19DB2FF8", 2172 | "Arn", 2173 | ], 2174 | }, 2175 | "/*", 2176 | ], 2177 | ], 2178 | }, 2179 | ], 2180 | }, 2181 | ], 2182 | "Version": "2012-10-17", 2183 | }, 2184 | }, 2185 | "Type": "AWS::S3::BucketPolicy", 2186 | }, 2187 | "TimeSleepCustomResourceHandler494c1b460d2f4e3b9bfd0b2cf10162f981117264": { 2188 | "DependsOn": [ 2189 | "TimeSleepCustomResourceHandler494c1b460d2f4e3b9bfd0b2cf10162f9ServiceRole47E386BD", 2190 | ], 2191 | "Properties": { 2192 | "Code": { 2193 | "ZipFile": ""use strict";var o=Object.defineProperty;var n=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var u=(t,e)=>{for(var s in e)o(t,s,{get:e[s],enumerable:!0})},R=(t,e,s,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of d(e))!p.call(t,r)&&r!==s&&o(t,r,{get:()=>e[r],enumerable:!(a=n(e,r))||a.enumerable});return t};var S=t=>R(o({},"__esModule",{value:!0}),t);var l={};u(l,{handler:()=>g});module.exports=S(l);var c=require("timers/promises"),g=async(t,e)=>{try{switch(t.RequestType){case"Create":await(0,c.setTimeout)(t.ResourceProperties.createDurationSeconds*1e3);break;case"Update":break;case"Delete":await(0,c.setTimeout)(t.ResourceProperties.destroyDurationSeconds*1e3);break}await i("SUCCESS",t,e)}catch(s){console.log(s),await i("FAILED",t,e,s.message)}},i=async(t,e,s,a)=>{let r=JSON.stringify({Status:t,Reason:a??"See the details in CloudWatch Log Stream: "+s.logStreamName,PhysicalResourceId:s.logStreamName,StackId:e.StackId,RequestId:e.RequestId,LogicalResourceId:e.LogicalResourceId,NoEcho:!1,Data:{}});await fetch(e.ResponseURL,{method:"PUT",body:r,headers:{"Content-Type":"","Content-Length":r.length.toString()}})};0&&(module.exports={handler}); 2194 | ", 2195 | }, 2196 | "Handler": "index.handler", 2197 | "MemorySize": 128, 2198 | "Role": { 2199 | "Fn::GetAtt": [ 2200 | "TimeSleepCustomResourceHandler494c1b460d2f4e3b9bfd0b2cf10162f9ServiceRole47E386BD", 2201 | "Arn", 2202 | ], 2203 | }, 2204 | "Runtime": "nodejs18.x", 2205 | "Timeout": 900, 2206 | }, 2207 | "Type": "AWS::Lambda::Function", 2208 | }, 2209 | "TimeSleepCustomResourceHandler494c1b460d2f4e3b9bfd0b2cf10162f9ServiceRole47E386BD": { 2210 | "Properties": { 2211 | "AssumeRolePolicyDocument": { 2212 | "Statement": [ 2213 | { 2214 | "Action": "sts:AssumeRole", 2215 | "Effect": "Allow", 2216 | "Principal": { 2217 | "Service": "lambda.amazonaws.com", 2218 | }, 2219 | }, 2220 | ], 2221 | "Version": "2012-10-17", 2222 | }, 2223 | "ManagedPolicyArns": [ 2224 | { 2225 | "Fn::Join": [ 2226 | "", 2227 | [ 2228 | "arn:", 2229 | { 2230 | "Ref": "AWS::Partition", 2231 | }, 2232 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 2233 | ], 2234 | ], 2235 | }, 2236 | ], 2237 | }, 2238 | "Type": "AWS::IAM::Role", 2239 | }, 2240 | "Vpc8378EB38": { 2241 | "Properties": { 2242 | "CidrBlock": "10.0.0.0/16", 2243 | "EnableDnsHostnames": true, 2244 | "EnableDnsSupport": true, 2245 | "InstanceTenancy": "default", 2246 | "Tags": [ 2247 | { 2248 | "Key": "Name", 2249 | "Value": "TestStack/Vpc", 2250 | }, 2251 | ], 2252 | }, 2253 | "Type": "AWS::EC2::VPC", 2254 | }, 2255 | "VpcIGWD7BA715C": { 2256 | "Properties": { 2257 | "Tags": [ 2258 | { 2259 | "Key": "Name", 2260 | "Value": "TestStack/Vpc", 2261 | }, 2262 | ], 2263 | }, 2264 | "Type": "AWS::EC2::InternetGateway", 2265 | }, 2266 | "VpcPrivateSubnet1DefaultRouteBE02A9ED": { 2267 | "Properties": { 2268 | "DestinationCidrBlock": "0.0.0.0/0", 2269 | "NatGatewayId": { 2270 | "Ref": "VpcPublicSubnet1NATGateway4D7517AA", 2271 | }, 2272 | "RouteTableId": { 2273 | "Ref": "VpcPrivateSubnet1RouteTableB2C5B500", 2274 | }, 2275 | }, 2276 | "Type": "AWS::EC2::Route", 2277 | }, 2278 | "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { 2279 | "Properties": { 2280 | "RouteTableId": { 2281 | "Ref": "VpcPrivateSubnet1RouteTableB2C5B500", 2282 | }, 2283 | "SubnetId": { 2284 | "Ref": "VpcPrivateSubnet1Subnet536B997A", 2285 | }, 2286 | }, 2287 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 2288 | }, 2289 | "VpcPrivateSubnet1RouteTableB2C5B500": { 2290 | "Properties": { 2291 | "Tags": [ 2292 | { 2293 | "Key": "Name", 2294 | "Value": "TestStack/Vpc/PrivateSubnet1", 2295 | }, 2296 | ], 2297 | "VpcId": { 2298 | "Ref": "Vpc8378EB38", 2299 | }, 2300 | }, 2301 | "Type": "AWS::EC2::RouteTable", 2302 | }, 2303 | "VpcPrivateSubnet1Subnet536B997A": { 2304 | "Properties": { 2305 | "AvailabilityZone": { 2306 | "Fn::Select": [ 2307 | 0, 2308 | { 2309 | "Fn::GetAZs": "", 2310 | }, 2311 | ], 2312 | }, 2313 | "CidrBlock": "10.0.128.0/18", 2314 | "MapPublicIpOnLaunch": false, 2315 | "Tags": [ 2316 | { 2317 | "Key": "aws-cdk:subnet-name", 2318 | "Value": "Private", 2319 | }, 2320 | { 2321 | "Key": "aws-cdk:subnet-type", 2322 | "Value": "Private", 2323 | }, 2324 | { 2325 | "Key": "Name", 2326 | "Value": "TestStack/Vpc/PrivateSubnet1", 2327 | }, 2328 | ], 2329 | "VpcId": { 2330 | "Ref": "Vpc8378EB38", 2331 | }, 2332 | }, 2333 | "Type": "AWS::EC2::Subnet", 2334 | }, 2335 | "VpcPrivateSubnet2DefaultRoute060D2087": { 2336 | "Properties": { 2337 | "DestinationCidrBlock": "0.0.0.0/0", 2338 | "NatGatewayId": { 2339 | "Ref": "VpcPublicSubnet2NATGateway9182C01D", 2340 | }, 2341 | "RouteTableId": { 2342 | "Ref": "VpcPrivateSubnet2RouteTableA678073B", 2343 | }, 2344 | }, 2345 | "Type": "AWS::EC2::Route", 2346 | }, 2347 | "VpcPrivateSubnet2RouteTableA678073B": { 2348 | "Properties": { 2349 | "Tags": [ 2350 | { 2351 | "Key": "Name", 2352 | "Value": "TestStack/Vpc/PrivateSubnet2", 2353 | }, 2354 | ], 2355 | "VpcId": { 2356 | "Ref": "Vpc8378EB38", 2357 | }, 2358 | }, 2359 | "Type": "AWS::EC2::RouteTable", 2360 | }, 2361 | "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { 2362 | "Properties": { 2363 | "RouteTableId": { 2364 | "Ref": "VpcPrivateSubnet2RouteTableA678073B", 2365 | }, 2366 | "SubnetId": { 2367 | "Ref": "VpcPrivateSubnet2Subnet3788AAA1", 2368 | }, 2369 | }, 2370 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 2371 | }, 2372 | "VpcPrivateSubnet2Subnet3788AAA1": { 2373 | "Properties": { 2374 | "AvailabilityZone": { 2375 | "Fn::Select": [ 2376 | 1, 2377 | { 2378 | "Fn::GetAZs": "", 2379 | }, 2380 | ], 2381 | }, 2382 | "CidrBlock": "10.0.192.0/18", 2383 | "MapPublicIpOnLaunch": false, 2384 | "Tags": [ 2385 | { 2386 | "Key": "aws-cdk:subnet-name", 2387 | "Value": "Private", 2388 | }, 2389 | { 2390 | "Key": "aws-cdk:subnet-type", 2391 | "Value": "Private", 2392 | }, 2393 | { 2394 | "Key": "Name", 2395 | "Value": "TestStack/Vpc/PrivateSubnet2", 2396 | }, 2397 | ], 2398 | "VpcId": { 2399 | "Ref": "Vpc8378EB38", 2400 | }, 2401 | }, 2402 | "Type": "AWS::EC2::Subnet", 2403 | }, 2404 | "VpcPublicSubnet1DefaultRoute3DA9E72A": { 2405 | "DependsOn": [ 2406 | "VpcVPCGWBF912B6E", 2407 | ], 2408 | "Properties": { 2409 | "DestinationCidrBlock": "0.0.0.0/0", 2410 | "GatewayId": { 2411 | "Ref": "VpcIGWD7BA715C", 2412 | }, 2413 | "RouteTableId": { 2414 | "Ref": "VpcPublicSubnet1RouteTable6C95E38E", 2415 | }, 2416 | }, 2417 | "Type": "AWS::EC2::Route", 2418 | }, 2419 | "VpcPublicSubnet1EIPD7E02669": { 2420 | "Properties": { 2421 | "Domain": "vpc", 2422 | "Tags": [ 2423 | { 2424 | "Key": "Name", 2425 | "Value": "TestStack/Vpc/PublicSubnet1", 2426 | }, 2427 | ], 2428 | }, 2429 | "Type": "AWS::EC2::EIP", 2430 | }, 2431 | "VpcPublicSubnet1NATGateway4D7517AA": { 2432 | "DependsOn": [ 2433 | "VpcPublicSubnet1DefaultRoute3DA9E72A", 2434 | "VpcPublicSubnet1RouteTableAssociation97140677", 2435 | ], 2436 | "Properties": { 2437 | "AllocationId": { 2438 | "Fn::GetAtt": [ 2439 | "VpcPublicSubnet1EIPD7E02669", 2440 | "AllocationId", 2441 | ], 2442 | }, 2443 | "SubnetId": { 2444 | "Ref": "VpcPublicSubnet1Subnet5C2D37C4", 2445 | }, 2446 | "Tags": [ 2447 | { 2448 | "Key": "Name", 2449 | "Value": "TestStack/Vpc/PublicSubnet1", 2450 | }, 2451 | ], 2452 | }, 2453 | "Type": "AWS::EC2::NatGateway", 2454 | }, 2455 | "VpcPublicSubnet1RouteTable6C95E38E": { 2456 | "Properties": { 2457 | "Tags": [ 2458 | { 2459 | "Key": "Name", 2460 | "Value": "TestStack/Vpc/PublicSubnet1", 2461 | }, 2462 | ], 2463 | "VpcId": { 2464 | "Ref": "Vpc8378EB38", 2465 | }, 2466 | }, 2467 | "Type": "AWS::EC2::RouteTable", 2468 | }, 2469 | "VpcPublicSubnet1RouteTableAssociation97140677": { 2470 | "Properties": { 2471 | "RouteTableId": { 2472 | "Ref": "VpcPublicSubnet1RouteTable6C95E38E", 2473 | }, 2474 | "SubnetId": { 2475 | "Ref": "VpcPublicSubnet1Subnet5C2D37C4", 2476 | }, 2477 | }, 2478 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 2479 | }, 2480 | "VpcPublicSubnet1Subnet5C2D37C4": { 2481 | "Properties": { 2482 | "AvailabilityZone": { 2483 | "Fn::Select": [ 2484 | 0, 2485 | { 2486 | "Fn::GetAZs": "", 2487 | }, 2488 | ], 2489 | }, 2490 | "CidrBlock": "10.0.0.0/18", 2491 | "MapPublicIpOnLaunch": false, 2492 | "Tags": [ 2493 | { 2494 | "Key": "aws-cdk:subnet-name", 2495 | "Value": "Public", 2496 | }, 2497 | { 2498 | "Key": "aws-cdk:subnet-type", 2499 | "Value": "Public", 2500 | }, 2501 | { 2502 | "Key": "Name", 2503 | "Value": "TestStack/Vpc/PublicSubnet1", 2504 | }, 2505 | ], 2506 | "VpcId": { 2507 | "Ref": "Vpc8378EB38", 2508 | }, 2509 | }, 2510 | "Type": "AWS::EC2::Subnet", 2511 | }, 2512 | "VpcPublicSubnet2DefaultRoute97F91067": { 2513 | "DependsOn": [ 2514 | "VpcVPCGWBF912B6E", 2515 | ], 2516 | "Properties": { 2517 | "DestinationCidrBlock": "0.0.0.0/0", 2518 | "GatewayId": { 2519 | "Ref": "VpcIGWD7BA715C", 2520 | }, 2521 | "RouteTableId": { 2522 | "Ref": "VpcPublicSubnet2RouteTable94F7E489", 2523 | }, 2524 | }, 2525 | "Type": "AWS::EC2::Route", 2526 | }, 2527 | "VpcPublicSubnet2EIP3C605A87": { 2528 | "Properties": { 2529 | "Domain": "vpc", 2530 | "Tags": [ 2531 | { 2532 | "Key": "Name", 2533 | "Value": "TestStack/Vpc/PublicSubnet2", 2534 | }, 2535 | ], 2536 | }, 2537 | "Type": "AWS::EC2::EIP", 2538 | }, 2539 | "VpcPublicSubnet2NATGateway9182C01D": { 2540 | "DependsOn": [ 2541 | "VpcPublicSubnet2DefaultRoute97F91067", 2542 | "VpcPublicSubnet2RouteTableAssociationDD5762D8", 2543 | ], 2544 | "Properties": { 2545 | "AllocationId": { 2546 | "Fn::GetAtt": [ 2547 | "VpcPublicSubnet2EIP3C605A87", 2548 | "AllocationId", 2549 | ], 2550 | }, 2551 | "SubnetId": { 2552 | "Ref": "VpcPublicSubnet2Subnet691E08A3", 2553 | }, 2554 | "Tags": [ 2555 | { 2556 | "Key": "Name", 2557 | "Value": "TestStack/Vpc/PublicSubnet2", 2558 | }, 2559 | ], 2560 | }, 2561 | "Type": "AWS::EC2::NatGateway", 2562 | }, 2563 | "VpcPublicSubnet2RouteTable94F7E489": { 2564 | "Properties": { 2565 | "Tags": [ 2566 | { 2567 | "Key": "Name", 2568 | "Value": "TestStack/Vpc/PublicSubnet2", 2569 | }, 2570 | ], 2571 | "VpcId": { 2572 | "Ref": "Vpc8378EB38", 2573 | }, 2574 | }, 2575 | "Type": "AWS::EC2::RouteTable", 2576 | }, 2577 | "VpcPublicSubnet2RouteTableAssociationDD5762D8": { 2578 | "Properties": { 2579 | "RouteTableId": { 2580 | "Ref": "VpcPublicSubnet2RouteTable94F7E489", 2581 | }, 2582 | "SubnetId": { 2583 | "Ref": "VpcPublicSubnet2Subnet691E08A3", 2584 | }, 2585 | }, 2586 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 2587 | }, 2588 | "VpcPublicSubnet2Subnet691E08A3": { 2589 | "Properties": { 2590 | "AvailabilityZone": { 2591 | "Fn::Select": [ 2592 | 1, 2593 | { 2594 | "Fn::GetAZs": "", 2595 | }, 2596 | ], 2597 | }, 2598 | "CidrBlock": "10.0.64.0/18", 2599 | "MapPublicIpOnLaunch": false, 2600 | "Tags": [ 2601 | { 2602 | "Key": "aws-cdk:subnet-name", 2603 | "Value": "Public", 2604 | }, 2605 | { 2606 | "Key": "aws-cdk:subnet-type", 2607 | "Value": "Public", 2608 | }, 2609 | { 2610 | "Key": "Name", 2611 | "Value": "TestStack/Vpc/PublicSubnet2", 2612 | }, 2613 | ], 2614 | "VpcId": { 2615 | "Ref": "Vpc8378EB38", 2616 | }, 2617 | }, 2618 | "Type": "AWS::EC2::Subnet", 2619 | }, 2620 | "VpcVPCGWBF912B6E": { 2621 | "Properties": { 2622 | "InternetGatewayId": { 2623 | "Ref": "VpcIGWD7BA715C", 2624 | }, 2625 | "VpcId": { 2626 | "Ref": "Vpc8378EB38", 2627 | }, 2628 | }, 2629 | "Type": "AWS::EC2::VPCGatewayAttachment", 2630 | }, 2631 | "WebServiceFargateService5BB9529D": { 2632 | "DependsOn": [ 2633 | "AlbListenerWeb0RuleE10BEE0F", 2634 | "WebServiceTaskTaskRoleDefaultPolicyC2E32007", 2635 | "WebServiceTaskTaskRole22AA8FAB", 2636 | ], 2637 | "Properties": { 2638 | "CapacityProviderStrategy": [ 2639 | { 2640 | "CapacityProvider": "FARGATE", 2641 | "Weight": 0, 2642 | }, 2643 | { 2644 | "CapacityProvider": "FARGATE_SPOT", 2645 | "Weight": 1, 2646 | }, 2647 | ], 2648 | "Cluster": { 2649 | "Ref": "ClusterEB0386A7", 2650 | }, 2651 | "DeploymentConfiguration": { 2652 | "Alarms": { 2653 | "AlarmNames": [], 2654 | "Enable": false, 2655 | "Rollback": false, 2656 | }, 2657 | "MaximumPercent": 200, 2658 | "MinimumHealthyPercent": 50, 2659 | }, 2660 | "EnableECSManagedTags": false, 2661 | "EnableExecuteCommand": true, 2662 | "HealthCheckGracePeriodSeconds": 60, 2663 | "LoadBalancers": [ 2664 | { 2665 | "ContainerName": "Main", 2666 | "ContainerPort": 3000, 2667 | "TargetGroupArn": { 2668 | "Ref": "AlbWebTargetGroupC65B2BDF", 2669 | }, 2670 | }, 2671 | ], 2672 | "NetworkConfiguration": { 2673 | "AwsvpcConfiguration": { 2674 | "AssignPublicIp": "DISABLED", 2675 | "SecurityGroups": [ 2676 | { 2677 | "Fn::GetAtt": [ 2678 | "WebServiceFargateServiceSecurityGroup60FE78D6", 2679 | "GroupId", 2680 | ], 2681 | }, 2682 | ], 2683 | "Subnets": [ 2684 | { 2685 | "Ref": "VpcPrivateSubnet1Subnet536B997A", 2686 | }, 2687 | { 2688 | "Ref": "VpcPrivateSubnet2Subnet3788AAA1", 2689 | }, 2690 | ], 2691 | }, 2692 | }, 2693 | "TaskDefinition": { 2694 | "Ref": "WebServiceTaskF9926E9D", 2695 | }, 2696 | }, 2697 | "Type": "AWS::ECS::Service", 2698 | }, 2699 | "WebServiceFargateServiceSecurityGroup60FE78D6": { 2700 | "DependsOn": [ 2701 | "WebServiceTaskTaskRoleDefaultPolicyC2E32007", 2702 | "WebServiceTaskTaskRole22AA8FAB", 2703 | ], 2704 | "Properties": { 2705 | "GroupDescription": "TestStack/WebService/FargateService/SecurityGroup", 2706 | "SecurityGroupEgress": [ 2707 | { 2708 | "CidrIp": "0.0.0.0/0", 2709 | "Description": "Allow all outbound traffic by default", 2710 | "IpProtocol": "-1", 2711 | }, 2712 | ], 2713 | "VpcId": { 2714 | "Ref": "Vpc8378EB38", 2715 | }, 2716 | }, 2717 | "Type": "AWS::EC2::SecurityGroup", 2718 | }, 2719 | "WebServiceFargateServiceSecurityGroupfromTestStackAlbSecurityGroup8A47F1DC30009EE54A2D": { 2720 | "DependsOn": [ 2721 | "WebServiceTaskTaskRoleDefaultPolicyC2E32007", 2722 | "WebServiceTaskTaskRole22AA8FAB", 2723 | ], 2724 | "Properties": { 2725 | "Description": "Load balancer to target", 2726 | "FromPort": 3000, 2727 | "GroupId": { 2728 | "Fn::GetAtt": [ 2729 | "WebServiceFargateServiceSecurityGroup60FE78D6", 2730 | "GroupId", 2731 | ], 2732 | }, 2733 | "IpProtocol": "tcp", 2734 | "SourceSecurityGroupId": { 2735 | "Fn::GetAtt": [ 2736 | "AlbSecurityGroup433229ED", 2737 | "GroupId", 2738 | ], 2739 | }, 2740 | "ToPort": 3000, 2741 | }, 2742 | "Type": "AWS::EC2::SecurityGroupIngress", 2743 | }, 2744 | "WebServiceTaskExecutionRole4406CA16": { 2745 | "Properties": { 2746 | "AssumeRolePolicyDocument": { 2747 | "Statement": [ 2748 | { 2749 | "Action": "sts:AssumeRole", 2750 | "Effect": "Allow", 2751 | "Principal": { 2752 | "Service": "ecs-tasks.amazonaws.com", 2753 | }, 2754 | }, 2755 | ], 2756 | "Version": "2012-10-17", 2757 | }, 2758 | }, 2759 | "Type": "AWS::IAM::Role", 2760 | }, 2761 | "WebServiceTaskExecutionRoleDefaultPolicyA297AC39": { 2762 | "Properties": { 2763 | "PolicyDocument": { 2764 | "Statement": [ 2765 | { 2766 | "Action": [ 2767 | "logs:CreateLogStream", 2768 | "logs:PutLogEvents", 2769 | ], 2770 | "Effect": "Allow", 2771 | "Resource": { 2772 | "Fn::GetAtt": [ 2773 | "WebServiceTaskMainLogGroup38DB76FC", 2774 | "Arn", 2775 | ], 2776 | }, 2777 | }, 2778 | ], 2779 | "Version": "2012-10-17", 2780 | }, 2781 | "PolicyName": "WebServiceTaskExecutionRoleDefaultPolicyA297AC39", 2782 | "Roles": [ 2783 | { 2784 | "Ref": "WebServiceTaskExecutionRole4406CA16", 2785 | }, 2786 | ], 2787 | }, 2788 | "Type": "AWS::IAM::Policy", 2789 | }, 2790 | "WebServiceTaskF9926E9D": { 2791 | "Properties": { 2792 | "ContainerDefinitions": [ 2793 | { 2794 | "Environment": [ 2795 | { 2796 | "Name": "LOG_LEVEL", 2797 | "Value": "ERROR", 2798 | }, 2799 | { 2800 | "Name": "DEBUG", 2801 | "Value": "false", 2802 | }, 2803 | { 2804 | "Name": "CONSOLE_API_URL", 2805 | "Value": "https://dify.example.com", 2806 | }, 2807 | { 2808 | "Name": "APP_API_URL", 2809 | "Value": "https://dify.example.com", 2810 | }, 2811 | { 2812 | "Name": "HOSTNAME", 2813 | "Value": "0.0.0.0", 2814 | }, 2815 | { 2816 | "Name": "PORT", 2817 | "Value": "3000", 2818 | }, 2819 | ], 2820 | "Essential": true, 2821 | "HealthCheck": { 2822 | "Command": [ 2823 | "CMD-SHELL", 2824 | "wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1", 2825 | ], 2826 | "Interval": 15, 2827 | "Retries": 3, 2828 | "StartPeriod": 30, 2829 | "Timeout": 5, 2830 | }, 2831 | "Image": "langgenius/dify-web:latest", 2832 | "LogConfiguration": { 2833 | "LogDriver": "awslogs", 2834 | "Options": { 2835 | "awslogs-group": { 2836 | "Ref": "WebServiceTaskMainLogGroup38DB76FC", 2837 | }, 2838 | "awslogs-region": { 2839 | "Ref": "AWS::Region", 2840 | }, 2841 | "awslogs-stream-prefix": "log", 2842 | }, 2843 | }, 2844 | "Name": "Main", 2845 | "PortMappings": [ 2846 | { 2847 | "ContainerPort": 3000, 2848 | "Protocol": "tcp", 2849 | }, 2850 | ], 2851 | }, 2852 | ], 2853 | "Cpu": "256", 2854 | "ExecutionRoleArn": { 2855 | "Fn::GetAtt": [ 2856 | "WebServiceTaskExecutionRole4406CA16", 2857 | "Arn", 2858 | ], 2859 | }, 2860 | "Family": "TestStackWebServiceTaskBC1EE4DB", 2861 | "Memory": "512", 2862 | "NetworkMode": "awsvpc", 2863 | "RequiresCompatibilities": [ 2864 | "FARGATE", 2865 | ], 2866 | "RuntimePlatform": { 2867 | "CpuArchitecture": "X86_64", 2868 | }, 2869 | "TaskRoleArn": { 2870 | "Fn::GetAtt": [ 2871 | "WebServiceTaskTaskRole22AA8FAB", 2872 | "Arn", 2873 | ], 2874 | }, 2875 | }, 2876 | "Type": "AWS::ECS::TaskDefinition", 2877 | }, 2878 | "WebServiceTaskMainLogGroup38DB76FC": { 2879 | "DeletionPolicy": "Retain", 2880 | "Type": "AWS::Logs::LogGroup", 2881 | "UpdateReplacePolicy": "Retain", 2882 | }, 2883 | "WebServiceTaskTaskRole22AA8FAB": { 2884 | "Properties": { 2885 | "AssumeRolePolicyDocument": { 2886 | "Statement": [ 2887 | { 2888 | "Action": "sts:AssumeRole", 2889 | "Effect": "Allow", 2890 | "Principal": { 2891 | "Service": "ecs-tasks.amazonaws.com", 2892 | }, 2893 | }, 2894 | ], 2895 | "Version": "2012-10-17", 2896 | }, 2897 | }, 2898 | "Type": "AWS::IAM::Role", 2899 | }, 2900 | "WebServiceTaskTaskRoleDefaultPolicyC2E32007": { 2901 | "Properties": { 2902 | "PolicyDocument": { 2903 | "Statement": [ 2904 | { 2905 | "Action": [ 2906 | "ssmmessages:CreateControlChannel", 2907 | "ssmmessages:CreateDataChannel", 2908 | "ssmmessages:OpenControlChannel", 2909 | "ssmmessages:OpenDataChannel", 2910 | ], 2911 | "Effect": "Allow", 2912 | "Resource": "*", 2913 | }, 2914 | { 2915 | "Action": "logs:DescribeLogGroups", 2916 | "Effect": "Allow", 2917 | "Resource": "*", 2918 | }, 2919 | { 2920 | "Action": [ 2921 | "logs:CreateLogStream", 2922 | "logs:DescribeLogStreams", 2923 | "logs:PutLogEvents", 2924 | ], 2925 | "Effect": "Allow", 2926 | "Resource": "*", 2927 | }, 2928 | ], 2929 | "Version": "2012-10-17", 2930 | }, 2931 | "PolicyName": "WebServiceTaskTaskRoleDefaultPolicyC2E32007", 2932 | "Roles": [ 2933 | { 2934 | "Ref": "WebServiceTaskTaskRole22AA8FAB", 2935 | }, 2936 | ], 2937 | }, 2938 | "Type": "AWS::IAM::Policy", 2939 | }, 2940 | "WorkerServiceFargateService83FD39CB": { 2941 | "DependsOn": [ 2942 | "WorkerServiceTaskTaskRoleDefaultPolicyB05718F5", 2943 | "WorkerServiceTaskTaskRoleC99169CE", 2944 | ], 2945 | "Properties": { 2946 | "CapacityProviderStrategy": [ 2947 | { 2948 | "CapacityProvider": "FARGATE", 2949 | "Weight": 0, 2950 | }, 2951 | { 2952 | "CapacityProvider": "FARGATE_SPOT", 2953 | "Weight": 1, 2954 | }, 2955 | ], 2956 | "Cluster": { 2957 | "Ref": "ClusterEB0386A7", 2958 | }, 2959 | "DeploymentConfiguration": { 2960 | "Alarms": { 2961 | "AlarmNames": [], 2962 | "Enable": false, 2963 | "Rollback": false, 2964 | }, 2965 | "MaximumPercent": 200, 2966 | "MinimumHealthyPercent": 50, 2967 | }, 2968 | "EnableECSManagedTags": false, 2969 | "EnableExecuteCommand": true, 2970 | "NetworkConfiguration": { 2971 | "AwsvpcConfiguration": { 2972 | "AssignPublicIp": "DISABLED", 2973 | "SecurityGroups": [ 2974 | { 2975 | "Fn::GetAtt": [ 2976 | "WorkerServiceFargateServiceSecurityGroupEC8152E8", 2977 | "GroupId", 2978 | ], 2979 | }, 2980 | ], 2981 | "Subnets": [ 2982 | { 2983 | "Ref": "VpcPrivateSubnet1Subnet536B997A", 2984 | }, 2985 | { 2986 | "Ref": "VpcPrivateSubnet2Subnet3788AAA1", 2987 | }, 2988 | ], 2989 | }, 2990 | }, 2991 | "TaskDefinition": { 2992 | "Ref": "WorkerServiceTaskEB3CD2E8", 2993 | }, 2994 | }, 2995 | "Type": "AWS::ECS::Service", 2996 | }, 2997 | "WorkerServiceFargateServiceSecurityGroupEC8152E8": { 2998 | "DependsOn": [ 2999 | "WorkerServiceTaskTaskRoleDefaultPolicyB05718F5", 3000 | "WorkerServiceTaskTaskRoleC99169CE", 3001 | ], 3002 | "Properties": { 3003 | "GroupDescription": "TestStack/WorkerService/FargateService/SecurityGroup", 3004 | "SecurityGroupEgress": [ 3005 | { 3006 | "CidrIp": "0.0.0.0/0", 3007 | "Description": "Allow all outbound traffic by default", 3008 | "IpProtocol": "-1", 3009 | }, 3010 | ], 3011 | "VpcId": { 3012 | "Ref": "Vpc8378EB38", 3013 | }, 3014 | }, 3015 | "Type": "AWS::EC2::SecurityGroup", 3016 | }, 3017 | "WorkerServiceTaskEB3CD2E8": { 3018 | "Properties": { 3019 | "ContainerDefinitions": [ 3020 | { 3021 | "Environment": [ 3022 | { 3023 | "Name": "MODE", 3024 | "Value": "worker", 3025 | }, 3026 | { 3027 | "Name": "LOG_LEVEL", 3028 | "Value": "ERROR", 3029 | }, 3030 | { 3031 | "Name": "DEBUG", 3032 | "Value": "false", 3033 | }, 3034 | { 3035 | "Name": "MIGRATION_ENABLED", 3036 | "Value": "true", 3037 | }, 3038 | { 3039 | "Name": "REDIS_HOST", 3040 | "Value": { 3041 | "Fn::GetAtt": [ 3042 | "RedisFF642DF2", 3043 | "PrimaryEndPoint.Address", 3044 | ], 3045 | }, 3046 | }, 3047 | { 3048 | "Name": "REDIS_PORT", 3049 | "Value": "6379", 3050 | }, 3051 | { 3052 | "Name": "REDIS_USE_SSL", 3053 | "Value": "true", 3054 | }, 3055 | { 3056 | "Name": "REDIS_DB", 3057 | "Value": "0", 3058 | }, 3059 | { 3060 | "Name": "STORAGE_TYPE", 3061 | "Value": "s3", 3062 | }, 3063 | { 3064 | "Name": "S3_BUCKET_NAME", 3065 | "Value": { 3066 | "Ref": "StorageBucket19DB2FF8", 3067 | }, 3068 | }, 3069 | { 3070 | "Name": "S3_REGION", 3071 | "Value": { 3072 | "Ref": "AWS::Region", 3073 | }, 3074 | }, 3075 | { 3076 | "Name": "DB_DATABASE", 3077 | "Value": "main", 3078 | }, 3079 | { 3080 | "Name": "VECTOR_STORE", 3081 | "Value": "pgvector", 3082 | }, 3083 | { 3084 | "Name": "PGVECTOR_DATABASE", 3085 | "Value": "pgvector", 3086 | }, 3087 | ], 3088 | "Essential": true, 3089 | "Image": "langgenius/dify-api:latest", 3090 | "LogConfiguration": { 3091 | "LogDriver": "awslogs", 3092 | "Options": { 3093 | "awslogs-group": { 3094 | "Ref": "WorkerServiceTaskMainLogGroupA90F15B8", 3095 | }, 3096 | "awslogs-region": { 3097 | "Ref": "AWS::Region", 3098 | }, 3099 | "awslogs-stream-prefix": "log", 3100 | }, 3101 | }, 3102 | "Name": "Main", 3103 | "Secrets": [ 3104 | { 3105 | "Name": "DB_USERNAME", 3106 | "ValueFrom": { 3107 | "Fn::Join": [ 3108 | "", 3109 | [ 3110 | { 3111 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3112 | }, 3113 | ":username::", 3114 | ], 3115 | ], 3116 | }, 3117 | }, 3118 | { 3119 | "Name": "DB_HOST", 3120 | "ValueFrom": { 3121 | "Fn::Join": [ 3122 | "", 3123 | [ 3124 | { 3125 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3126 | }, 3127 | ":host::", 3128 | ], 3129 | ], 3130 | }, 3131 | }, 3132 | { 3133 | "Name": "DB_PORT", 3134 | "ValueFrom": { 3135 | "Fn::Join": [ 3136 | "", 3137 | [ 3138 | { 3139 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3140 | }, 3141 | ":port::", 3142 | ], 3143 | ], 3144 | }, 3145 | }, 3146 | { 3147 | "Name": "DB_PASSWORD", 3148 | "ValueFrom": { 3149 | "Fn::Join": [ 3150 | "", 3151 | [ 3152 | { 3153 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3154 | }, 3155 | ":password::", 3156 | ], 3157 | ], 3158 | }, 3159 | }, 3160 | { 3161 | "Name": "PGVECTOR_USER", 3162 | "ValueFrom": { 3163 | "Fn::Join": [ 3164 | "", 3165 | [ 3166 | { 3167 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3168 | }, 3169 | ":username::", 3170 | ], 3171 | ], 3172 | }, 3173 | }, 3174 | { 3175 | "Name": "PGVECTOR_HOST", 3176 | "ValueFrom": { 3177 | "Fn::Join": [ 3178 | "", 3179 | [ 3180 | { 3181 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3182 | }, 3183 | ":host::", 3184 | ], 3185 | ], 3186 | }, 3187 | }, 3188 | { 3189 | "Name": "PGVECTOR_PORT", 3190 | "ValueFrom": { 3191 | "Fn::Join": [ 3192 | "", 3193 | [ 3194 | { 3195 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3196 | }, 3197 | ":port::", 3198 | ], 3199 | ], 3200 | }, 3201 | }, 3202 | { 3203 | "Name": "PGVECTOR_PASSWORD", 3204 | "ValueFrom": { 3205 | "Fn::Join": [ 3206 | "", 3207 | [ 3208 | { 3209 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3210 | }, 3211 | ":password::", 3212 | ], 3213 | ], 3214 | }, 3215 | }, 3216 | { 3217 | "Name": "REDIS_PASSWORD", 3218 | "ValueFrom": { 3219 | "Ref": "RedisAuthToken9E34F6A5", 3220 | }, 3221 | }, 3222 | { 3223 | "Name": "CELERY_BROKER_URL", 3224 | "ValueFrom": { 3225 | "Fn::Join": [ 3226 | "", 3227 | [ 3228 | "arn:", 3229 | { 3230 | "Ref": "AWS::Partition", 3231 | }, 3232 | ":ssm:", 3233 | { 3234 | "Ref": "AWS::Region", 3235 | }, 3236 | ":", 3237 | { 3238 | "Ref": "AWS::AccountId", 3239 | }, 3240 | ":parameter/", 3241 | { 3242 | "Ref": "RedisBrokerUrlA8582E06", 3243 | }, 3244 | ], 3245 | ], 3246 | }, 3247 | }, 3248 | { 3249 | "Name": "SECRET_KEY", 3250 | "ValueFrom": { 3251 | "Ref": "ApiServiceEncryptionSecretF73F9ECD", 3252 | }, 3253 | }, 3254 | ], 3255 | }, 3256 | ], 3257 | "Cpu": "1024", 3258 | "ExecutionRoleArn": { 3259 | "Fn::GetAtt": [ 3260 | "WorkerServiceTaskExecutionRoleFF871AC9", 3261 | "Arn", 3262 | ], 3263 | }, 3264 | "Family": "TestStackWorkerServiceTask7B4B1923", 3265 | "Memory": "2048", 3266 | "NetworkMode": "awsvpc", 3267 | "RequiresCompatibilities": [ 3268 | "FARGATE", 3269 | ], 3270 | "RuntimePlatform": { 3271 | "CpuArchitecture": "X86_64", 3272 | }, 3273 | "TaskRoleArn": { 3274 | "Fn::GetAtt": [ 3275 | "WorkerServiceTaskTaskRoleC99169CE", 3276 | "Arn", 3277 | ], 3278 | }, 3279 | }, 3280 | "Type": "AWS::ECS::TaskDefinition", 3281 | }, 3282 | "WorkerServiceTaskExecutionRoleDefaultPolicyD3383FFB": { 3283 | "Properties": { 3284 | "PolicyDocument": { 3285 | "Statement": [ 3286 | { 3287 | "Action": [ 3288 | "logs:CreateLogStream", 3289 | "logs:PutLogEvents", 3290 | ], 3291 | "Effect": "Allow", 3292 | "Resource": { 3293 | "Fn::GetAtt": [ 3294 | "WorkerServiceTaskMainLogGroupA90F15B8", 3295 | "Arn", 3296 | ], 3297 | }, 3298 | }, 3299 | { 3300 | "Action": [ 3301 | "secretsmanager:GetSecretValue", 3302 | "secretsmanager:DescribeSecret", 3303 | ], 3304 | "Effect": "Allow", 3305 | "Resource": { 3306 | "Ref": "PostgresClusterSecretAttachment8DDCF2A8", 3307 | }, 3308 | }, 3309 | { 3310 | "Action": [ 3311 | "secretsmanager:GetSecretValue", 3312 | "secretsmanager:DescribeSecret", 3313 | ], 3314 | "Effect": "Allow", 3315 | "Resource": { 3316 | "Ref": "RedisAuthToken9E34F6A5", 3317 | }, 3318 | }, 3319 | { 3320 | "Action": [ 3321 | "ssm:DescribeParameters", 3322 | "ssm:GetParameters", 3323 | "ssm:GetParameter", 3324 | "ssm:GetParameterHistory", 3325 | ], 3326 | "Effect": "Allow", 3327 | "Resource": { 3328 | "Fn::Join": [ 3329 | "", 3330 | [ 3331 | "arn:", 3332 | { 3333 | "Ref": "AWS::Partition", 3334 | }, 3335 | ":ssm:", 3336 | { 3337 | "Ref": "AWS::Region", 3338 | }, 3339 | ":", 3340 | { 3341 | "Ref": "AWS::AccountId", 3342 | }, 3343 | ":parameter/", 3344 | { 3345 | "Ref": "RedisBrokerUrlA8582E06", 3346 | }, 3347 | ], 3348 | ], 3349 | }, 3350 | }, 3351 | { 3352 | "Action": [ 3353 | "secretsmanager:GetSecretValue", 3354 | "secretsmanager:DescribeSecret", 3355 | ], 3356 | "Effect": "Allow", 3357 | "Resource": { 3358 | "Ref": "ApiServiceEncryptionSecretF73F9ECD", 3359 | }, 3360 | }, 3361 | ], 3362 | "Version": "2012-10-17", 3363 | }, 3364 | "PolicyName": "WorkerServiceTaskExecutionRoleDefaultPolicyD3383FFB", 3365 | "Roles": [ 3366 | { 3367 | "Ref": "WorkerServiceTaskExecutionRoleFF871AC9", 3368 | }, 3369 | ], 3370 | }, 3371 | "Type": "AWS::IAM::Policy", 3372 | }, 3373 | "WorkerServiceTaskExecutionRoleFF871AC9": { 3374 | "Properties": { 3375 | "AssumeRolePolicyDocument": { 3376 | "Statement": [ 3377 | { 3378 | "Action": "sts:AssumeRole", 3379 | "Effect": "Allow", 3380 | "Principal": { 3381 | "Service": "ecs-tasks.amazonaws.com", 3382 | }, 3383 | }, 3384 | ], 3385 | "Version": "2012-10-17", 3386 | }, 3387 | }, 3388 | "Type": "AWS::IAM::Role", 3389 | }, 3390 | "WorkerServiceTaskMainLogGroupA90F15B8": { 3391 | "DeletionPolicy": "Retain", 3392 | "Type": "AWS::Logs::LogGroup", 3393 | "UpdateReplacePolicy": "Retain", 3394 | }, 3395 | "WorkerServiceTaskTaskRoleC99169CE": { 3396 | "Properties": { 3397 | "AssumeRolePolicyDocument": { 3398 | "Statement": [ 3399 | { 3400 | "Action": "sts:AssumeRole", 3401 | "Effect": "Allow", 3402 | "Principal": { 3403 | "Service": "ecs-tasks.amazonaws.com", 3404 | }, 3405 | }, 3406 | ], 3407 | "Version": "2012-10-17", 3408 | }, 3409 | }, 3410 | "Type": "AWS::IAM::Role", 3411 | }, 3412 | "WorkerServiceTaskTaskRoleDefaultPolicyB05718F5": { 3413 | "Properties": { 3414 | "PolicyDocument": { 3415 | "Statement": [ 3416 | { 3417 | "Action": [ 3418 | "s3:GetObject*", 3419 | "s3:GetBucket*", 3420 | "s3:List*", 3421 | "s3:DeleteObject*", 3422 | "s3:PutObject", 3423 | "s3:PutObjectLegalHold", 3424 | "s3:PutObjectRetention", 3425 | "s3:PutObjectTagging", 3426 | "s3:PutObjectVersionTagging", 3427 | "s3:Abort*", 3428 | ], 3429 | "Effect": "Allow", 3430 | "Resource": [ 3431 | { 3432 | "Fn::GetAtt": [ 3433 | "StorageBucket19DB2FF8", 3434 | "Arn", 3435 | ], 3436 | }, 3437 | { 3438 | "Fn::Join": [ 3439 | "", 3440 | [ 3441 | { 3442 | "Fn::GetAtt": [ 3443 | "StorageBucket19DB2FF8", 3444 | "Arn", 3445 | ], 3446 | }, 3447 | "/*", 3448 | ], 3449 | ], 3450 | }, 3451 | ], 3452 | }, 3453 | { 3454 | "Action": [ 3455 | "bedrock:InvokeModel", 3456 | "bedrock:InvokeModelWithResponseStream", 3457 | ], 3458 | "Effect": "Allow", 3459 | "Resource": "*", 3460 | }, 3461 | { 3462 | "Action": [ 3463 | "ssmmessages:CreateControlChannel", 3464 | "ssmmessages:CreateDataChannel", 3465 | "ssmmessages:OpenControlChannel", 3466 | "ssmmessages:OpenDataChannel", 3467 | ], 3468 | "Effect": "Allow", 3469 | "Resource": "*", 3470 | }, 3471 | { 3472 | "Action": "logs:DescribeLogGroups", 3473 | "Effect": "Allow", 3474 | "Resource": "*", 3475 | }, 3476 | { 3477 | "Action": [ 3478 | "logs:CreateLogStream", 3479 | "logs:DescribeLogStreams", 3480 | "logs:PutLogEvents", 3481 | ], 3482 | "Effect": "Allow", 3483 | "Resource": "*", 3484 | }, 3485 | ], 3486 | "Version": "2012-10-17", 3487 | }, 3488 | "PolicyName": "WorkerServiceTaskTaskRoleDefaultPolicyB05718F5", 3489 | "Roles": [ 3490 | { 3491 | "Ref": "WorkerServiceTaskTaskRoleC99169CE", 3492 | }, 3493 | ], 3494 | }, 3495 | "Type": "AWS::IAM::Policy", 3496 | }, 3497 | }, 3498 | "Rules": { 3499 | "CheckBootstrapVersion": { 3500 | "Assertions": [ 3501 | { 3502 | "Assert": { 3503 | "Fn::Not": [ 3504 | { 3505 | "Fn::Contains": [ 3506 | [ 3507 | "1", 3508 | "2", 3509 | "3", 3510 | "4", 3511 | "5", 3512 | ], 3513 | { 3514 | "Ref": "BootstrapVersion", 3515 | }, 3516 | ], 3517 | }, 3518 | ], 3519 | }, 3520 | "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", 3521 | }, 3522 | ], 3523 | }, 3524 | }, 3525 | } 3526 | `; 3527 | -------------------------------------------------------------------------------- /test/dify-on-aws.test.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { Template } from 'aws-cdk-lib/assertions'; 3 | import { DifyOnAwsStack } from '../lib/dify-on-aws-stack'; 4 | 5 | test('Snapshot test', () => { 6 | const app = new cdk.App(); 7 | const stack = new DifyOnAwsStack(app, 'TestStack', { 8 | allowedCidrs: ['0.0.0.0/0'], 9 | difySandboxImageTag: '0.2.4', 10 | domainName: 'example.com', 11 | hostedZoneId: 'Z0123456789ABCDEFG', 12 | allowAnySyscalls: true, 13 | }); 14 | const template = Template.fromStack(stack); 15 | expect(template).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2020", 7 | "dom" 8 | ], 9 | "declaration": true, 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "noImplicitThis": true, 14 | "alwaysStrict": true, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": false, 19 | "inlineSourceMap": true, 20 | "inlineSources": true, 21 | "experimentalDecorators": true, 22 | "strictPropertyInitialization": false, 23 | "typeRoots": [ 24 | "./node_modules/@types" 25 | ] 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "cdk.out" 30 | ] 31 | } 32 | --------------------------------------------------------------------------------