@aws-cdk/core#Construct TypeScript Examples

The following examples show how to use @aws-cdk/core#Construct. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: globalCodeCommit.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
export class GlobalCodeCommit extends Construct {
    repository: Repository;

    static getCodeRepositoryName(envSettings: EnvironmentSettings) {
        return `${envSettings.projectName}-code`;
    }

    static getCodeRepoUserNameOutputExportName(envSettings: EnvironmentSettings) {
        return `${envSettings.projectName}-codeRepoUserName`
    }

    static getCodeRepoCloneUrlHttpOutputExportName(envSettings: EnvironmentSettings) {
        return `${envSettings.projectName}-codeRepoCloneUrlHttp`
    }

    constructor(scope: Construct, id: string, props: EnvConstructProps) {
        super(scope, id);

        this.repository = new Repository(this, 'CodeRepo', {
            repositoryName: GlobalCodeCommit.getCodeRepositoryName(props.envSettings),
            description: `${props.envSettings.projectName} code mirror repository used to source CodePipeline`
        });

        const user = new User(this, 'CodeRepoUser', {
            userName: `${props.envSettings.projectName}-code`
        });
        this.repository.grantPullPush(user);

        new CfnOutput(this, "CodeRepoUserName", {
            exportName: GlobalCodeCommit.getCodeRepoUserNameOutputExportName(props.envSettings),
            value: user.userName,
        });

        new CfnOutput(this, "CodeRepoCloneUrlHttp", {
            exportName: GlobalCodeCommit.getCodeRepoCloneUrlHttpOutputExportName(props.envSettings),
            value: this.repository.repositoryCloneUrlHttp,
        });
    }
}
Example #2
Source File: datadog-integration-aws.ts    From cdk-datadog-resources with Apache License 2.0 6 votes vote down vote up
constructor(scope: Construct, id: string, props: DatadogIntegrationAWSProps) {
    const cfnProperties = camelcaseKeys(props, {
      deep: true,
      pascalCase: true,
    });
    // @ts-ignore
    delete Object.assign(cfnProperties, { AccountID: cfnProperties.AccountId }).AccountId;
    new CfnResource(scope, id, {
      type: 'Datadog::Integrations::AWS',
      properties: { ...cfnProperties },
    });
  }
Example #3
Source File: globalECR.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
export class GlobalECR extends Construct {
    backendRepository: Repository;

    static getBackendRepositoryName(envSettings: EnvironmentSettings) {
        return `${envSettings.projectName}-backend`;
    }

    constructor(scope: Construct, id: string, props: EnvConstructProps) {
        super(scope, id);

        this.backendRepository = new Repository(this, "ECRBackendRepository", {
            repositoryName: GlobalECR.getBackendRepositoryName(props.envSettings),
        });
    }
}
Example #4
Source File: certificates-stack.ts    From hasura-cdk with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: CertificatesStackProps) {
        super(scope, id, props);

        const hostedZone = PublicHostedZone.fromHostedZoneAttributes(this, 'HasuraHostedZone', {
            hostedZoneId: props.hostedZoneId,
            zoneName: props.hostedZoneName,
        });

        const hasura = new DnsValidatedCertificate(this, 'HasuraCertificate', {
            hostedZone,
            domainName: props.hasuraHostname,
        });

        const actions = new DnsValidatedCertificate(this, 'ActionsCertificate', {
            hostedZone,
            domainName: props.actionsHostname,
        });


        this.certificates = {
            hasura,
            actions,
        };

    }
Example #5
Source File: function.ts    From aws-cdk-webpack-lambda-function with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: WebpackFunctionProps) {
    const { runtime, handlerDir, outputBasename, handler } = preProcess(props);

    super(scope, id, {
      ...props,
      runtime,
      code: Code.fromAsset(handlerDir),
      handler: `${outputBasename}.${handler}`,
    });
  }
Example #6
Source File: auth-stack.ts    From MDDL with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props)
    const { userPoolName, emailSender, customDomain } = props

    // create the user pool
    const { userPool, uiCustomization } = this.addUserPool(
      userPoolName,
      emailSender,
    )
    this.userPoolId = userPool.userPoolId

    // attach the custom domain
    if (customDomain) {
      this.addCustomDomain(userPool, customDomain, uiCustomization)
      this.authUrl = `https://${customDomain.domain}`
    } else {
      this.authUrl = userPool.userPoolProviderUrl
    }

    this.addTriggers(userPool)

    // SNS Topics for SES Bounce Event
    new Topic(this, 'SesBounceTopic', {
      displayName: 'Bounce notifications topic',
    })

    // SNS Topics for SES Complaint Event
    new Topic(this, 'SesComplaintsTopic', {
      displayName: 'Complaints notifications topic',
    })

    // SNS Topics for SES Delivery Event
    new Topic(this, 'SesDeliveryTopic', {
      displayName: 'Delivery Notifications topic',
    })
  }
Example #7
Source File: datadog-dashboard.ts    From cdk-datadog-resources with Apache License 2.0 6 votes vote down vote up
constructor(scope: Construct, id: string, props: DatadogDashboardProps) {
    const cfnProperties = camelcaseKeys(props, {
      deep: true,
      pascalCase: true,
    });
    new CfnResource(scope, id, {
      type: 'Datadog::Dashboards::Dashboard',
      properties: { ...cfnProperties },
    });
  }
Example #8
Source File: ciEntrypoint.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: CiEntrypointProps) {
        super(scope, id);

        this.artifactsBucket = new Bucket(this, "ArtifactsBucket", {
            versioned: true,
        });
        this.codeBuildProject = this.createBuildProject(this.artifactsBucket, props);

        const deployBranches = props.envSettings.deployBranches;
        if (deployBranches.length > 0) {
            this.triggerFunction = new Function(this, 'TriggerLambda', {
                runtime: Runtime.NODEJS_12_X,
                handler: 'index.handler',
                code: Code.fromAsset(path.join(__dirname, 'functions', 'trigger-entrypoint')),
                environment: {
                    PROJECT_ENV_NAME: props.envSettings.projectEnvName,
                    DEPLOY_BRANCHES: JSON.stringify(deployBranches),
                    PROJECT_NAME: this.codeBuildProject.projectName
                }
            });

            this.triggerFunction.addToRolePolicy(new PolicyStatement({
                actions: [
                    'codebuild:StartBuild',
                ],
                resources: [this.codeBuildProject.projectArn]
            }));
            props.codeRepository.onCommit('OnDeployCommit', {
                target: new targets.LambdaFunction(this.triggerFunction)
            });
        }
    }
Example #9
Source File: api-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
constructor(scope: Construct, id: string, props: LambdaStackProps) {
    super(scope, id, props);

    this.rdsPassword = Secret.fromSecretNameV2(
      this,
      "rdsPassword",
      "rdsPassword"
    );

    this.handler = new Function(this, "graphql", {
      runtime: Runtime.NODEJS_14_X,
      code: Code.fromAsset("app"),
      handler: "build/src/graphql.handler",
      vpc: props.vpc,
      vpcSubnets: {
        subnetType: SubnetType.ISOLATED,
      },
      securityGroup: SecurityGroup.fromSecurityGroupId(
        this,
        "inboundDbAccessSecurityGroup" + "rdsLambda",
        props.inboundDbAccessSecurityGroup
      ),

      environment: {
        TYPEORM_URL: `postgres://${
          props.rdsDbUser
        }:${this.rdsPassword.secretValue.toString()}@${props.rdsEndpoint}:${
          props.rdsPort
        }/${props.rdsDbName}`,
        TYPEORM_SYNCHRONIZE: "true",
        TYPEORM_LOGGING: "true",
        TYPEORM_ENTITIES: "./build/src/entity/*.entity.js",
      },
    });

    this.api = new LambdaRestApi(this, "graphql-api", {
      handler: this.handler,
      proxy: false,
    });

    this.graphql = this.api.root.addResource("graphql");
    this.graphql.addMethod("ANY");

    this.apiPathOutput = new CfnOutput(this, "apiPath", {
      value: this.api.root.path,
      description: "Path of the API",
    });
  }
Example #10
Source File: amazon-efs-integrations-stack.ts    From amazon-efs-integrations with MIT No Attribution 5 votes vote down vote up
constructor(scope: Construct, id: string, props: AmazonEfsIntegrationsStackProps) {
    if (props.createEfsAccessPoints && !props.createEfsFilesystem) {
      throw new Error('`createEfsFileSystem` must be set to true if `createEfsAccessPoints` is true');
    }

    super(scope, id, props);

    const vpc = new Vpc(this, 'EfsIntegrationDemo', {maxAzs: 2});
    const efsSecurityGroup = new SecurityGroup(this, 'EfsSecurityGroup', {securityGroupName: 'efs-demo-fs', vpc});

    let fileSystem;
    let efsAccessPoints;

    if (props.createEfsFilesystem) {
      /* tslint:disable-next-line:no-unused-expression */
      fileSystem = new FileSystem(this, 'EfsIntegrationDemoFileSystem', {
        encrypted: true,
        fileSystemName: 'efs-demo-fs',
        securityGroup: efsSecurityGroup,
        vpc,
      });

      if (props.createEfsAccessPoints) {
        efsAccessPoints = new EfsAccessPoints(
          fileSystem,
          props.createEcsOnEc2Service,
          props.createEcsOnFargateService,
        );
      }
    }

    if (props.createEcsOnEc2Service || props.createEcsOnFargateService) {
      const cluster = new Cluster(this, 'EcsCluster', {vpc});
      let ecsOnEc2Service;
      let ecsOnFargateService;

      if (props.createEcsOnEc2Service) {
        cluster.addCapacity('DefaultAutoScalingGroup', {
          instanceType: new InstanceType('t2.large'),
          maxCapacity: 2,
          minCapacity: 2,
        });

        ecsOnEc2Service = EcsEfsIntegrationService.create(
          ServiceType.EC2,
          cluster,
          fileSystem,
          efsAccessPoints
        ) as ApplicationLoadBalancedEc2Service;
        efsSecurityGroup.connections.allowFrom(ecsOnEc2Service.service, Port.tcp(2049));
      }

      if (props.createEcsOnFargateService) {
        ecsOnFargateService = EcsEfsIntegrationService.create(
          ServiceType.FARGATE,
          cluster,
          fileSystem,
          efsAccessPoints
        ) as ApplicationLoadBalancedFargateService;
        efsSecurityGroup.connections.allowFrom(ecsOnFargateService.service, Port.tcp(2049));
      }

      if (props.createEfsAccessPoints && fileSystem && efsAccessPoints) {
        // tslint:disable-next-line: no-unused-expression
        new EfsFileSystemPolicy(
          fileSystem,
          efsAccessPoints,
          ecsOnEc2Service,
          ecsOnFargateService,
        );
      }
    }
  }
Example #11
Source File: actions-stack.ts    From hasura-cdk with MIT License 5 votes vote down vote up
constructor(scope: Construct, id: string, props: ActionsStackProps) {
        super(scope, id, props);

        const hostedZone = PublicHostedZone.fromHostedZoneAttributes(this, 'HasuraHostedZone', {
            hostedZoneId: props.hostedZoneId,
            zoneName: props.hostedZoneName,
        });

        const api = new RestApi(this, 'ActionsApi', {
            domainName: {
                domainName: props.actionsHostname,
                certificate: props.certificates.actions,
            },
            restApiName: 'Actions',
            description: 'Endpoint For Hasura Actions',
            deployOptions: {
                loggingLevel: MethodLoggingLevel.INFO,
                dataTraceEnabled: true,
            },
        });

        // API DNS record
        new ARecord(this, 'ActionsApiAliasRecord', {
            zone: hostedZone,
            recordName: props.actionsHostname,
            target: AddressRecordTarget.fromAlias(new route53_targets.ApiGateway(api)),
        });

        // Create a lambda layer to contain node_modules
        const handlerDependenciesLayer = new RetainedLambdaLayerVersion(this, 'ActionHandlerDependencies', {
            contentLocation: 'actions/dependencies-layer',
            description: 'Dependencies layer',
            compatibleRuntimes: [Runtime.NODEJS_12_X],
        });

        const actionHandler = new Function(this, 'ActionHandler', {
            functionName: `${props.appName}-ActionHandler`,
            handler: 'handler.handler',
            memorySize: 1024,
            runtime: Runtime.NODEJS_12_X,
            code: Code.fromAsset(path.join(__dirname, '../../actions/dist/')),
            timeout: Duration.seconds(4),
            layers: [handlerDependenciesLayer],
        });

        const handlerResource = api.root.addResource('handler');
        const actionHandlerIntegration = new LambdaIntegration(actionHandler);

        handlerResource.addMethod('POST', actionHandlerIntegration);
        handlerResource.addMethod('GET', actionHandlerIntegration);

    }
Example #12
Source File: applicationMultipleTargetGroupsFargateService.ts    From aws-boilerplate with MIT License 5 votes vote down vote up
/**
     * Constructs a new instance of the ApplicationMultipleTargetGroupsFargateService class.
     */
    constructor(scope: Construct, id: string, props: ApplicationMultipleTargetGroupsFargateServiceProps) {
        super(scope, id, props);

        this.assignPublicIp = props.assignPublicIp !== undefined ? props.assignPublicIp : false;
        if (props.taskDefinition && props.taskImageOptions) {
            throw new Error('You must specify only one of TaskDefinition or TaskImageOptions.');
        } else if (props.taskDefinition) {
            this.taskDefinition = props.taskDefinition;
        } else if (props.taskImageOptions) {
            const taskImageOptions = props.taskImageOptions;
            this.taskDefinition = new FargateTaskDefinition(this, 'TaskDef', {
                memoryLimitMiB: props.memoryLimitMiB,
                cpu: props.cpu,
                executionRole: props.executionRole,
                taskRole: props.taskRole,
                family: props.family,

            });

            for (const taskImageOptionsProps of taskImageOptions) {
                const containerName = taskImageOptionsProps.containerName !== undefined ? taskImageOptionsProps.containerName : 'web';
                const container = this.taskDefinition.addContainer(containerName, {
                    image: taskImageOptionsProps.image,
                    logging: this.logDriver,
                    environment: taskImageOptionsProps.environment,
                    secrets: taskImageOptionsProps.secrets,
                    command: ["sh", "-c", "/bin/chamber exec $CHAMBER_SERVICE_NAME -- ./scripts/run.sh"]
                });
                if (taskImageOptionsProps.containerPorts) {
                    for (const containerPort of taskImageOptionsProps.containerPorts) {
                        container.addPortMappings({
                            containerPort
                        });
                    }
                }
            }
        } else {
            throw new Error('You must specify one of: taskDefinition or image');
        }
        if (!this.taskDefinition.defaultContainer) {
            throw new Error('At least one essential container must be specified');
        }
        if (this.taskDefinition.defaultContainer.portMappings.length === 0) {
            this.taskDefinition.defaultContainer.addPortMappings({
                containerPort: 80
            });
        }
        this.service = this.createFargateService(props);
        if (props.targetGroups) {
            this.addPortMappingForTargets(this.taskDefinition.defaultContainer, props.targetGroups);
            this.targetGroup = this.registerECSTargets(this.service, this.taskDefinition.defaultContainer, props.targetGroups);
        }
    }
Example #13
Source File: data-store-stack.ts    From MDDL with MIT License 4 votes vote down vote up
constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props)

    // read out config and set defaults
    const { vpcConfig = {}, rdsConfig = {}, providedKmsKey } = props
    const {
      cidrBlock = '10.0.0.0/16',
      maxAzs = 2,
      natGatewaysCount = 2,
    } = vpcConfig
    const {
      backupRetentionDays = 30,
      minCapacity = 1,
      maxCapacity = 8,
    } = rdsConfig

    let kmsKey: IKey
    if (providedKmsKey) {
      // import key
      kmsKey = Key.fromKeyArn(this, 'ProvidedKey', providedKmsKey.keyArn)
    } else {
      // create new KMS key for the data store to use
      kmsKey = new Key(this, 'Key', {
        description: `KMS Key for ${this.stackName} stack`,
        enableKeyRotation: true,
      })

      // allow RDS to use the KMS key
      // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.Keys.html
      kmsKey.addToResourcePolicy(
        new PolicyStatement({
          actions: ['kms:Decrypt', 'kms:GenerateDataKey*'],
          resources: ['*'],
          principals: [new AnyPrincipal()],
          conditions: {
            StringEquals: {
              'kms:ViaService': `rds.${this.region}.amazonaws.com`,
              'kms:CallerAccount': this.account,
            },
          },
        }),
      )
    }

    // create the VPC
    this.vpc = new Vpc(this, 'Vpc', {
      cidr: cidrBlock,
      maxAzs: maxAzs,
      enableDnsHostnames: false,
      enableDnsSupport: true,
      natGateways: natGatewaysCount,
      subnetConfiguration: [
        {
          cidrMask: 28,
          name: 'isolated',
          subnetType: SubnetType.ISOLATED,
        },
        {
          cidrMask: 28,
          name: 'private',
          subnetType: SubnetType.PRIVATE,
        },
        {
          cidrMask: 28,
          name: 'public',
          subnetType: SubnetType.PUBLIC,
        },
      ],
    })

    // create the root DB credentials
    const rdsRootCredentialsSecret = new Secret(
      this,
      'RdsClusterCredentialsSecret',
      {
        secretName: `${this.stackName}-rds-root-credentials`,
        generateSecretString: {
          secretStringTemplate: JSON.stringify({
            username: 'root',
          }),
          excludePunctuation: true,
          includeSpace: false,
          generateStringKey: 'password',
          excludeCharacters: '"@/\\',
          passwordLength: 30,
        },
        encryptionKey: kmsKey,
      },
    )

    // configure RDS subnet group
    const rdsSubnetGroup = new CfnDBSubnetGroup(this, 'RdsSubnetGroup', {
      dbSubnetGroupDescription: `Subnet group for RDS ${this.stackName}`,
      subnetIds: this.vpc.isolatedSubnets.map((s) => s.subnetId),
    })

    // configure RDS security group
    const rdsSecurityGroup = new SecurityGroup(this, 'RdsSecurityGroup', {
      vpc: this.vpc,
      allowAllOutbound: false,
      description: `${this.stackName} security group for RDS`,
    })

    // create the DB cluster
    const rdsPort = 3306
    const databaseName = 'root'
    const rdsCluster = new CfnDBCluster(this, 'RdsCluster', {
      engineMode: 'serverless',
      engine: 'aurora-mysql',
      engineVersion: '5.7.mysql_aurora.2.07.1',
      enableHttpEndpoint: true,
      databaseName,
      kmsKeyId: kmsKey.keyId,
      masterUsername: rdsRootCredentialsSecret
        .secretValueFromJson('username')
        .toString(),
      masterUserPassword: rdsRootCredentialsSecret
        .secretValueFromJson('password')
        .toString(),
      backupRetentionPeriod: backupRetentionDays,
      scalingConfiguration: {
        maxCapacity,
        minCapacity,
        autoPause: false,
      },
      deletionProtection: true,
      dbSubnetGroupName: rdsSubnetGroup.ref,
      dbClusterParameterGroupName: 'default.aurora-mysql5.7',
      // The following three lines cause errors on updates, so they are commented out.
      //  If you need custom values for them, uncomment before first deployment.
      // preferredMaintenanceWindow: maintenanceWindowWeekly,
      // preferredBackupWindow: backupWindowDaily,
      // port: rdsPort,
      storageEncrypted: true,
      vpcSecurityGroupIds: [rdsSecurityGroup.securityGroupId],
    })
    rdsCluster.applyRemovalPolicy(RemovalPolicy.SNAPSHOT)
    rdsCluster.node.addDependency(this.vpc, kmsKey)
    this.rdsEndpoint = rdsCluster.attrEndpointAddress

    // add database networking
    this.rdsAccessSecurityGroup = new SecurityGroup(
      this,
      'RdsAccessSecurityGroup',
      {
        vpc: this.vpc,
        description: `${this.stackName} security group for RDS Access`,
      },
    )
    rdsSecurityGroup.addIngressRule(
      this.rdsAccessSecurityGroup,
      Port.tcp(rdsPort),
    )
    rdsSecurityGroup.addEgressRule(
      this.rdsAccessSecurityGroup,
      Port.tcp(rdsPort),
    )
    this.rdsAccessSecurityGroup.addIngressRule(
      rdsSecurityGroup,
      Port.tcp(rdsPort),
    )

    const { layer: mysqlLayer } = this.addMysqlLayer()

    // configure function used by the city stacks to create a new DB and user within this cluster
    this.createDbUserFunction = new Function(this, 'CreateDbUserFunction', {
      code: Code.fromAsset(path.join('build', 'create-db-and-user.zip')),
      handler: 'index.handler',
      runtime: Runtime.NODEJS_12_X,
      vpc: this.vpc,
      timeout: Duration.seconds(60),
      securityGroups: [this.rdsAccessSecurityGroup],
      layers: [mysqlLayer],
      environment: {
        DB_HOST: rdsCluster.attrEndpointAddress,
        DB_USER: rdsRootCredentialsSecret
          .secretValueFromJson('username')
          .toString(),
        DB_PASSWORD: rdsRootCredentialsSecret
          .secretValueFromJson('password')
          .toString(),
        DB_DEFAULT_DATABASE: databaseName,
      },
    })
  }
Example #14
Source File: fargateServiceResources.ts    From aws-boilerplate with MIT License 4 votes vote down vote up
export class FargateServiceResources extends Construct {
    mainVpc: IVpc;
    mainCluster: ICluster;
    backendRepository: IRepository;
    publicLoadBalancer: IApplicationLoadBalancer;
    publicLoadBalancerSecurityGroup: ISecurityGroup;
    fargateContainerSecurityGroup: ISecurityGroup;

    private readonly envSettings: EnvironmentSettings;

    constructor(scope: Construct, id: string, props: EnvConstructProps) {
        super(scope, id);

        this.envSettings = props.envSettings;

        this.mainVpc = this.retrieveMainVpc();
        this.fargateContainerSecurityGroup = this.retrieveFargateContainerSecurityGroup();
        this.mainCluster = this.retrieveMainCluster(this.mainVpc);
        this.publicLoadBalancer = this.retrievePublicLoadBalancer(this.mainVpc);
        this.publicLoadBalancerSecurityGroup = this.retrievePublicLoadBalancerSecurityGroup(props);
        this.backendRepository = this.retrieveBackendECRRepositories();
    }

    private retrieveMainVpc() {
        const stack = Stack.of(this);

        return Vpc.fromVpcAttributes(this, "EC2MainVpc", {
            vpcId: Fn.importValue(MainVpc.getVpcArnOutputExportName(this.envSettings)),
            publicSubnetIds: [
                Fn.importValue(MainVpc.getPublicSubnetOneIdOutputExportName(this.envSettings)),
                Fn.importValue(MainVpc.getPublicSubnetTwoIdOutputExportName(this.envSettings)),
            ],
            availabilityZones: [
                stack.availabilityZones[0],
                stack.availabilityZones[1],
            ],
        });
    }

    private retrieveFargateContainerSecurityGroup() {
        return SecurityGroup.fromSecurityGroupId(this, "FargateContainerSecurityGroup",
            Fn.importValue(MainECSCluster.getFargateContainerSecurityGroupIdOutputExportName(this.envSettings)));
    }

    private retrieveMainCluster(vpc: IVpc) {
        return Cluster.fromClusterAttributes(this, "ECSMainCluster", {
            vpc,
            securityGroups: [],
            clusterName: MainECSCluster.getClusterName(this.envSettings),
        });
    }

    private retrievePublicLoadBalancer(vpc: IVpc) {
        const securityGroupId = Fn.importValue(
            MainECSCluster.getPublicLoadBalancerSecurityGroupIdOutputExportName(this.envSettings));
        const loadBalancerArn = Fn.importValue(MainECSCluster.getLoadBalancerArnOutputExportName(this.envSettings));
        const loadBalancerDnsName = Fn.importValue(MainECSCluster.getLoadBalancerDnsNameOutput(this.envSettings));
        const loadBalancerCanonicalHostedZoneId = Fn.importValue(
            MainECSCluster.getLoadBalancerCanonicalHostedZoneIdOutputExportName(this.envSettings));

        return ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(this,
            "MainPublicLoadBalancer", {
                vpc,
                loadBalancerArn,
                securityGroupId,
                loadBalancerDnsName,
                loadBalancerCanonicalHostedZoneId
            });
    }

    private retrievePublicLoadBalancerSecurityGroup(props: EnvConstructProps): ISecurityGroup {
        return SecurityGroup.fromSecurityGroupId(this, "LbSecurityGroup",
            Fn.importValue(MainECSCluster.getPublicLoadBalancerSecurityGroupIdOutputExportName(props.envSettings)));

    }

    private retrieveBackendECRRepositories() {
        return Repository.fromRepositoryName(this, "ECRBackendRepository",
            GlobalECR.getBackendRepositoryName(this.envSettings));
    }
}
Example #15
Source File: city-stack.ts    From MDDL with MIT License 4 votes vote down vote up
constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props)

    // initialise properties
    this.bucketNames = {}

    // read out config
    const {
      dataStoreStack,
      authStack,
      apiDomainConfig,
      webAppDomainConfig,
      hostedZoneAttributes,
      jwtAuth,
      additionalCallbackUrls = [],
      emailSender,
      agencyEmailDomainsWhitelist = [],
      monitoring = {},
      throttling = {},
      providedKmsKey,
    } = props

    // check jwt auth is given if auth stack is not
    if (!authStack && !jwtAuth) {
      throw new Error(
        'jwtAuth must be provided when authStack is not provided in stack ' +
          this.stackName,
      )
    }

    // read in the signing key parameter if its provided
    if (jwtAuth && jwtAuth.signingKeyParameterPath) {
      const signingKeyParameter = StringParameter.fromStringParameterName(
        this,
        'SigningKeyParameter',
        jwtAuth.signingKeyParameterPath,
      )
      this.environmentVariables[EnvironmentVariables.AUTH_SIGNING_KEY] =
        signingKeyParameter.stringValue
    } else if (jwtAuth && jwtAuth.integrationType === 'NYCID_OAUTH') {
      // verify requirements for NYC.ID
      throw new Error(
        'jwtConfiguration.signingKeyParameterPath must be provided for NYCID_OAUTH integration',
      )
    }

    // check data store stack is given - it is "optional" to allow for configuration
    // to be put together at runtime
    if (!dataStoreStack) {
      throw new Error('dataStoreStack must be provided')
    }

    // set VPC details for lambda access
    const { vpc, rdsAccessSecurityGroup, rdsEndpoint } = dataStoreStack
    this.lambdaVpc = vpc
    this.lambdaSecurityGroups = [rdsAccessSecurityGroup]
    this.rdsEndpoint = rdsEndpoint

    // check the cloudfront certificate is in north virginia
    if (
      webAppDomainConfig &&
      webAppDomainConfig.certificateArn &&
      !webAppDomainConfig.certificateArn.toLowerCase().includes('us-east-1')
    ) {
      throw new Error(
        'webAppDomainConfig.certificateArn must be a certificate in us-east-1',
      )
    }

    // reference hosted zone
    const hostedZone: IHostedZone | undefined = hostedZoneAttributes
      ? HostedZone.fromHostedZoneAttributes(
          this,
          `HostedZone`,
          hostedZoneAttributes,
        )
      : undefined

    // add hosting for the web app
    const { domain: webAppDomain } = this.addHosting(
      'WebApp',
      webAppDomainConfig,
      hostedZone,
    )

    // add auth stack integration
    const { jwtConfiguration } = this.addAuthIntegration(
      `https://${webAppDomain}`,
      [`https://${webAppDomain}/authorize`, ...additionalCallbackUrls],
      [`https://${webAppDomain}`, ...additionalCallbackUrls],
      authStack,
      apiDomainConfig,
      jwtAuth,
    )

    // create/reference the city key used for encryption of resources in this stack
    if (providedKmsKey) {
      this.kmsKey = Key.fromKeyArn(this, 'ProvidedKey', providedKmsKey.keyArn)
    } else {
      const { kmsKey } = this.addKmsKey()
      this.kmsKey = kmsKey
    }

    // create the DB and access credentials for this city
    const { secret } = this.addDbAndCredentials(
      this.kmsKey,
      dataStoreStack.createDbUserFunction,
    )

    // create the mysql lambda layer
    const { layer: mySqlLayer } = this.addMysqlLayer()

    // create the graphics magick lambda layer
    const { layer: gmLayer } = this.addGraphicsMagickLayer()

    // add api
    const { api, authorizer, corsOrigins, defaultStage } = this.addApi(
      webAppDomain,
      jwtConfiguration,
      apiDomainConfig,
      hostedZone,
    )
    const apiProps: ApiProps = {
      api,
      authorizer,
      dbSecret: secret,
      mySqlLayer,
      gmLayer,
    }

    // create dead letter queue
    const { queue: deadLetterQueue } = this.createDeadLetterQueue(this.kmsKey)

    // create uploads bucket
    this.uploadsBucket = this.createUploadsBucket(
      this.kmsKey,
      corsOrigins,
    ).bucket

    // create audit log group and queue
    this.auditLogGroup = this.createAuditLogGroup(this.kmsKey).logGroup
    this.auditLogQueue = this.createAuditLogQueue(
      this.kmsKey,
      deadLetterQueue,
    ).queue

    // create email processing queue
    this.emailProcessorQueue = this.createEmailProcessorQueue(
      this.kmsKey,
      deadLetterQueue,
    ).queue

    this.environmentVariables = {
      ...this.environmentVariables,
      [EnvironmentVariables.DOCUMENTS_BUCKET]: this.uploadsBucket.bucketName,
      [EnvironmentVariables.USERINFO_ENDPOINT]:
        jwtConfiguration.userInfoEndpoint,
      [EnvironmentVariables.WEB_APP_DOMAIN]: webAppDomain,
      [EnvironmentVariables.EMAIL_SENDER]: `${emailSender.name} <${emailSender.address}>`,
      [EnvironmentVariables.AGENCY_EMAIL_DOMAINS_WHITELIST]: agencyEmailDomainsWhitelist.join(
        ',',
      ),
      [EnvironmentVariables.ACTIVITY_CLOUDWATCH_LOG_GROUP]: this.auditLogGroup
        .logGroupName,
      [EnvironmentVariables.ACTIVITY_RECORD_SQS_QUEUE_URL]: this.auditLogQueue
        .queueUrl,
      [EnvironmentVariables.EMAIL_PROCESSOR_SQS_QUEUE_URL]: this
        .emailProcessorQueue.queueUrl,
      [EnvironmentVariables.ENVIRONMENT_NAME]: this.stackName,
      [EnvironmentVariables.AUTH_INTEGRATION_TYPE]: jwtConfiguration.integrationType
        ? jwtConfiguration.integrationType
        : 'OAUTH',
    }

    if (jwtConfiguration.emailUnverifiedRedirectEndpoint) {
      this.environmentVariables[
        EnvironmentVariables.AUTH_EMAIL_UNVERIFIED_REDIRECT
      ] = jwtConfiguration.emailUnverifiedRedirectEndpoint
    }

    if (monitoring) {
      const { sentryDsn } = monitoring
      if (sentryDsn) {
        this.environmentVariables[EnvironmentVariables.SENTRY_DSN] = sentryDsn
      }
    }

    this.createAuditLogQueueProcessor(this.auditLogQueue)
    this.createEmailQueueProcessor(
      this.emailProcessorQueue,
      emailSender.address,
    )

    // add user routes
    this.addUserRoutes(apiProps)

    // add document routes
    this.addDocumentRoutes(apiProps)

    // add collection routes
    this.addCollectionRoutes(apiProps)

    // add account delegate routes
    this.addAccountDelegateRoutes(apiProps)

    // add database migrations
    this.runMigrations(secret, mySqlLayer)

    // set up throttling
    this.configureThrottling(defaultStage, throttling)

    // set up alerts
    if (monitoring.alertsSnsTopicArn) {
      this.configureAlerts(monitoring.alertsSnsTopicArn, api, deadLetterQueue, [
        this.auditLogQueue,
        this.emailProcessorQueue,
      ])
    }
  }
Example #16
Source File: ciPipeline.ts    From aws-boilerplate with MIT License 4 votes vote down vote up
export class CiPipeline extends Construct {
    buildStageName = 'Build';
    deployStageName = 'Deploy';

    static getSourceOutputArtifact(envSettings: EnvironmentSettings) {
        return Artifact.artifact(`${envSettings.projectEnvName}-source`)
    }

    constructor(scope: Construct, id: string, props: CiPipelineProps) {
        super(scope, id);

        const pipeline = this.createPipeline(props);
        this.configureEnv(pipeline, props);
    }

    private configureEnv(pipeline: Pipeline, props: CiPipelineProps) {
        const sourceOutputArtifact = CiPipeline.getSourceOutputArtifact(props.envSettings);
        const buildStage = this.selectStage(this.buildStageName, pipeline);
        const deployStage = this.selectStage(this.deployStageName, pipeline);

        new ComponentsCiConfig(this, "ComponentsConfig", {
            buildStage,
            deployStage,
            envSettings: props.envSettings,
            inputArtifact: sourceOutputArtifact,
        });

        new BackendCiConfig(this, "BackendConfig", {
            buildStage,
            deployStage,
            envSettings: props.envSettings,
            inputArtifact: sourceOutputArtifact,
            backendRepository: props.backendRepository,
        });

        new WebappCiConfig(this, "WebAppConfig", {
            envSettings: props.envSettings,
            buildStage,
            deployStage,
            inputArtifact: sourceOutputArtifact,
        });

        new ServerlessCiConfig(this, "WorkersConfig", {
            name: 'workers',
            envSettings: props.envSettings,
            buildStage,
            deployStage,
            inputArtifact: sourceOutputArtifact,
        });

        new UploadVersionCiConfig(this, "UploadVersionConfig", {
            envSettings: props.envSettings,
            buildStage,
            deployStage,
            inputArtifact: sourceOutputArtifact,
        });
    }

    private selectStage(name: string, pipeline: Pipeline) {
        const stage = pipeline.stages.find(stage => stage.stageName === name);

        if (!stage) {
            throw Error(`Stage ${name} hasn't been found!`);
        }

        return stage;
    }

    private createPipeline(props: CiPipelineProps) {
        return new Pipeline(this, "Pipeline", {
            pipelineName: `${props.envSettings.projectEnvName}-ci`,
            stages: [{
                stageName: "Source",
                actions: [
                    new S3SourceAction({
                        actionName: `${props.envSettings.projectEnvName}-source`,
                        bucket: props.entrypointArtifactBucket,
                        bucketKey: CiEntrypoint.getArtifactsName(props.envSettings),
                        output: CiPipeline.getSourceOutputArtifact(props.envSettings),
                        trigger: S3Trigger.POLL,
                    }),
                ],
            }, {
                stageName: this.buildStageName,
                actions: [],
            }, {
                stageName: this.deployStageName,
                actions: [],
            }],
        });
    }
}
Example #17
Source File: hasura-stack.ts    From hasura-cdk with MIT License 4 votes vote down vote up
constructor(scope: Construct, id: string, props: HasuraStackProps) {
        super(scope, id, props);

        const hostedZone = PublicHostedZone.fromHostedZoneAttributes(this, 'HasuraHostedZone', {
            hostedZoneId: props.hostedZoneId,
            zoneName: props.hostedZoneName,
        });


        const hasuraDatabaseName = props.appName;

        const hasuraDatabase = new DatabaseInstance(this, 'HasuraDatabase', {
            instanceIdentifier: props.appName,
            databaseName: hasuraDatabaseName,
            engine: DatabaseInstanceEngine.POSTGRES,
            instanceType: InstanceType.of(InstanceClass.BURSTABLE3, InstanceSize.MICRO),
            masterUsername: 'syscdk',
            storageEncrypted: true,
            allocatedStorage: 20,
            maxAllocatedStorage: 100,
            vpc: props.vpc,
            deletionProtection: false,
            multiAz: props.multiAz,
            removalPolicy: RemovalPolicy.DESTROY,
        });

        const hasuraUsername = 'hasura';

        const hasuraUserSecret = new DatabaseSecret(this, 'HasuraDatabaseUser', {
            username: hasuraUsername,
            masterSecret: hasuraDatabase.secret,

        });
        hasuraUserSecret.attach(hasuraDatabase); // Adds DB connections information in the secret

        // Output the Endpoint Address so it can be used in post-deploy
        new CfnOutput(this, 'HasuraDatabaseUserSecretArn', {
            value: hasuraUserSecret.secretArn,
        });

        new CfnOutput(this, 'HasuraDatabaseMasterSecretArn', {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            value: hasuraDatabase.secret!.secretArn,
        });

        const hasuraDatabaseUrlSecret = new Secret(this, 'HasuraDatabaseUrlSecret', {
            secretName: `${props.appName}-HasuraDatabaseUrl`,
        });


        new CfnOutput(this, 'HasuraDatabaseUrlSecretArn', {
            value: hasuraDatabaseUrlSecret.secretArn,
        });

        const hasuraAdminSecret = new Secret(this, 'HasuraAdminSecret', {
            secretName: `${props.appName}-HasuraAdminSecret`,
        });

        new CfnOutput(this, 'HasuraAdminSecretArn', {
            value: hasuraAdminSecret.secretArn,
        });

        const hasuraJwtSecret = new Secret(this, 'HasuraJwtSecret', {
            secretName: `${props.appName}-HasuraJWTSecret`,
        });

        new CfnOutput(this, 'HasuraJwtSecretArn', {
            value: hasuraJwtSecret.secretArn,
        });


        // Create a load-balanced Fargate service and make it public
        const fargate = new ApplicationLoadBalancedFargateService(this, 'HasuraFargateService', {
            serviceName: props.appName,
            vpc: props.vpc,
            cpu: 256,
            desiredCount: props.multiAz ? 2 : 1,
            taskImageOptions: {
                image: ContainerImage.fromRegistry('hasura/graphql-engine:v1.2.1'),
                containerPort: 8080,
                enableLogging: true,
                environment: {
                    HASURA_GRAPHQL_ENABLE_CONSOLE: 'true',
                    HASURA_GRAPHQL_PG_CONNECTIONS: '100',
                    HASURA_GRAPHQL_LOG_LEVEL: 'debug',
                },
                secrets: {
                    HASURA_GRAPHQL_DATABASE_URL: ECSSecret.fromSecretsManager(hasuraDatabaseUrlSecret),
                    HASURA_GRAPHQL_ADMIN_SECRET: ECSSecret.fromSecretsManager(hasuraAdminSecret),
                    HASURA_GRAPHQL_JWT_SECRET: ECSSecret.fromSecretsManager(hasuraJwtSecret),
                },
            },
            memoryLimitMiB: 512,
            publicLoadBalancer: true, // Default is false
            certificate: props.certificates.hasura,
            domainName: props.hasuraHostname,
            domainZone: hostedZone,
            assignPublicIp: true,
        });

        fargate.targetGroup.configureHealthCheck({
            enabled: true,
            path: '/healthz',
            healthyHttpCodes: '200',
        });

        hasuraDatabase.connections.allowFrom(fargate.service, new Port({
            protocol: Protocol.TCP,
            stringRepresentation: 'Postgres Port',
            fromPort: 5432,
            toPort: 5432,
        }));
    }
Example #18
Source File: applicationMultipleTargetGroupsFargateServiceBase.ts    From aws-boilerplate with MIT License 4 votes vote down vote up
/**
 * The base class for ApplicationMultipleTargetGroupsEc2Service and ApplicationMultipleTargetGroupsFargateService classes.
 */
export abstract class ApplicationMultipleTargetGroupsServiceBase extends Construct {
    /**
     * The desired number of instantiations of the task definition to keep running on the service.
     */
    readonly desiredCount: number;
    /**
     * The default Application Load Balancer for the service (first added load balancer).
     */
    readonly loadBalancer: IApplicationLoadBalancer;
    /**
     * The default listener for the service (first added listener).
     */
    readonly listener: IApplicationListener;
    /**
     * The cluster that hosts the service.
     */
    readonly cluster: ICluster;
    protected logDriver?: LogDriver;
    protected listeners: IApplicationListener[];
    protected targetGroups: ApplicationTargetGroup[];
    private readonly loadBalancers: IApplicationLoadBalancer[];

    /**
     * Constructs a new instance of the ApplicationMultipleTargetGroupsServiceBase class.
     */
    protected constructor(scope: Construct, id: string, props: ApplicationMultipleTargetGroupsServiceBaseProps) {
        super(scope, id);
        this.listeners = [];
        this.targetGroups = [];
        this.loadBalancers = [];
        this.validateInput(props);

        this.cluster = props.cluster;

        this.desiredCount = props.desiredCount || 1;
        if (props.taskImageOptions) {
            for (const taskImageOptionsProps of props.taskImageOptions) {
                this.logDriver = this.createLogDriver(taskImageOptionsProps.enableLogging, taskImageOptionsProps.logDriver);
            }
        }

        for (const lbProps of props.loadBalancers) {
            const internetFacing = lbProps.publicLoadBalancer !== undefined ? lbProps.publicLoadBalancer : true;
            const {loadBalancer: lb} = lbProps;

            this.loadBalancers.push(lb);

            for (const listener of lbProps.listeners) {
                this.listeners.push(listener);
            }
            this.createDomainName(lb, internetFacing, lbProps.domainName, lbProps.domainZone);
        }
        // set up default load balancer and listener.
        this.loadBalancer = this.loadBalancers[0];
        this.listener = this.listeners[0];
    }

    protected createAWSLogDriver(prefix: string): AwsLogDriver {
        return new AwsLogDriver({streamPrefix: prefix});
    }

    protected findListener(name?: string): IApplicationListener {
        if (!name) {
            return this.listener;
        }
        for (const listener of this.listeners) {
            if (listener.node.id === name) {
                return listener;
            }
        }
        throw new Error(`Listener ${name} is not defined. Did you define listener with name ${name}?`);
    }

    protected registerECSTargets(service: BaseService, container: ContainerDefinition, targets: ApplicationTargetProps[]): ApplicationTargetGroup {

        targets?.forEach((targetProps, index) => {
            const idSuffix = index > 0 ? index : '';
            const targetGroup = new ApplicationTargetGroup(this, `TargetGroup${idSuffix}`, {
                vpc: service.cluster.vpc,
                port: targetProps.containerPort,
                healthCheck: {
                    path: '/lbcheck',
                    protocol: ELBProtocol.HTTP,
                    interval: Duration.seconds(6),
                    timeout: Duration.seconds(5),
                    healthyThresholdCount: 2,
                    unhealthyThresholdCount: 2,
                },
                deregistrationDelay: Duration.seconds(10),
                targetType: TargetType.IP,
                targets: [
                    service.loadBalancerTarget({
                        containerName: container.containerName,
                        containerPort: targetProps.containerPort,
                        protocol: targetProps.protocol,
                    }),
                ],
            });

            this.findListener(targetProps.listener).addTargetGroups(`ECSTargetGroup${idSuffix}${container.containerName}${targetProps.containerPort}`, {
                hostHeader: targetProps.hostHeader,
                pathPattern: targetProps.pathPattern,
                priority: targetProps.priority,
                targetGroups: [targetGroup],
            });
            this.targetGroups.push(targetGroup);
        });

        if (this.targetGroups.length === 0) {
            throw new Error('At least one target group should be specified.');
        }
        return this.targetGroups[0];
    }

    protected addPortMappingForTargets(container: ContainerDefinition, targets: ApplicationTargetProps[]): void {
        for (const target of targets) {
            if (!container.findPortMapping(target.containerPort, target.protocol || Protocol.TCP)) {
                container.addPortMappings({
                    containerPort: target.containerPort,
                    protocol: target.protocol
                });
            }
        }
    }

    /**
     * Create log driver if logging is enabled.
     */
    private createLogDriver(enableLoggingProp?: boolean, logDriverProp?: LogDriver) {
        const enableLogging = enableLoggingProp !== undefined ? enableLoggingProp : true;
        return logDriverProp !== undefined
            ? logDriverProp : enableLogging
                ? this.createAWSLogDriver(this.node.id) : undefined;
    }

    private validateInput(props: ApplicationMultipleTargetGroupsServiceBaseProps) {
        if (props.cluster && props.vpc) {
            throw new Error('You can only specify either vpc or cluster. Alternatively, you can leave both blank');
        }
        if (props.desiredCount !== undefined && props.desiredCount < 1) {
            throw new Error('You must specify a desiredCount greater than 0');
        }
        if (props.loadBalancers) {
            if (props.loadBalancers.length === 0) {
                throw new Error('At least one load balancer must be specified');
            }
            for (const lbProps of props.loadBalancers) {
                if (lbProps.listeners.length === 0) {
                    throw new Error('At least one listener must be specified');
                }
            }
        }
    }

    private createDomainName(loadBalancer: IApplicationLoadBalancer, internetFacing: boolean, name?: string, zone?: IHostedZone) {
        let domainName = loadBalancer.loadBalancerDnsName;
        if (typeof name !== 'undefined') {
            if (typeof zone === 'undefined') {
                throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name');
            }
            if (internetFacing) {
                const record = new ARecord(this, `DNS${loadBalancer.node.id}`, {
                    zone,
                    recordName: name,
                    target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
                });
                domainName = record.domainName;
            }
        }
        return domainName;
    }
}
Example #19
Source File: index.ts    From aws-cdk-dynamodb-seeder with Apache License 2.0 4 votes vote down vote up
export class Seeder extends Construct {
  protected props: Props;
  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id);
    if (!props.setup || !Array.isArray(props.setup)) throw new Error('setup value must be an array of JSON objects');
    this.props = props;

    const destinationBucket = new Bucket(this, 'acds-bucket', {
      removalPolicy: RemovalPolicy.DESTROY,
    });
    tmp.setGracefulCleanup();
    tmp.dir((err, dir) => {
      if (err) throw err;
      this.writeTempFile(dir, 'setup.json', props.setup);
      if (props.teardown) {
        this.writeTempFile(dir, 'teardown.json', props.teardown);
      }
      new BucketDeployment(this, id, {
        sources: [Source.asset(dir)],
        destinationBucket,
        retainOnDelete: false,
      });
    });

    const fn = new Function(this, 'handler', {
      runtime: Runtime.NODEJS_12_X,
      handler: 'index.handler',
      timeout: Duration.seconds(900),
      code: Code.fromInline(`
console.log('function loaded');

const AWS = require('aws-sdk');
const s3 = new AWS.S3();

const writeTypeFromAction = (action) => {
  if (action === "Put")
    return "Item";
  if (action === "Delete")
    return "Key";
}

const run = async (filename, action) => {
  console.log('reading from s3');
  const data = await s3.getObject({
    Bucket: "${destinationBucket.bucketName}", 
    Key: filename
  }).promise();
  console.log('finished reading from s3');
  
  console.log('transforming seed data');
  const seed = JSON.parse(data.Body.toString());
  console.log('finished transforming seed data');
  
  const documentClient = new AWS.DynamoDB.DocumentClient({
    convertEmptyValues: true
  });
  console.log('sending data to dynamodb');
  do {
    const requests = [];
    const batch = seed.splice(0, 25);
    for (let i = 0; i < batch.length; i++) {
      requests.push({
        [action + "Request"]: {
          [writeTypeFromAction(action)]: batch[i]
        }
      });
    }
    await documentClient.batchWrite({
      RequestItems: {
        '${props.table.tableName}': [...requests]
      }
    }).promise();
  }
  while (seed.length > 0);
  console.log('finished sending data to dynamodb');
}

exports.handler = async (event) => {
  if (event.mode === "delete")
    await run("teardown.json", "Delete");
  if (event.mode === "create" || event.mode === "update")
    await run("setup.json", "Put");
}`),
    });
    destinationBucket.grantRead(fn);
    props.table.grantWriteData(fn);

    const onEvent = new AwsCustomResource(this, 'on-event', {
      onCreate: {
        ...this.callLambdaOptions(),
        parameters: {
          FunctionName: fn.functionArn,
          InvokeArgs: JSON.stringify({
            mode: 'create',
          }),
        },
      },
      onDelete: props.teardown
        ? {
            ...this.callLambdaOptions(),
            parameters: {
              FunctionName: fn.functionArn,
              InvokeArgs: JSON.stringify({
                mode: 'delete',
              }),
            },
          }
        : undefined,
      onUpdate: props.refreshOnUpdate
        ? {
            ...this.callLambdaOptions(),
            parameters: {
              FunctionName: fn.functionArn,
              InvokeArgs: JSON.stringify({
                mode: 'update',
              }),
            },
          }
        : undefined,
      policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE }),
    });
    fn.grantInvoke(onEvent);
  }

  private callLambdaOptions(): AwsSdkCall {
    return {
      service: 'Lambda',
      action: 'invokeAsync',
      apiVersion: '2015-03-31',
      physicalResourceId: {
        id: `${this.props.table.tableArn}-seeder`,
      },
    };
  }

  private writeTempFile(dir: string, filename: string, data: Item[] | ItemKey[]): void {
    const buffer = Buffer.from(JSON.stringify(data));
    const filepath = dir + '/' + filename;
    fs.writeFileSync(filepath, buffer);
  }
}
Example #20
Source File: stack.ts    From keycloak-on-aws with Apache License 2.0 4 votes vote down vote up
constructor(scope: Construct, id: string, props: KeycloakStackProps = {}) {
    super(scope, id, props);

    const dbMsg = props.auroraServerless ? 'using aurora serverless' : 'rds mysql';
    const vpcMsg = props.fromExistingVPC ? 'existing vpc' : 'new vpc';

    this.setDescription(`(SO8021) - Deploy keycloak ${dbMsg} with ${vpcMsg}. template version: ${process.env.VERSION}`);

    const certificateArnParam = this.makeParam('CertificateArn', {
      type: 'String',
      description: 'Certificate Arn for Application Load Balancer',
      minLength: 5,
    });

    this.addGroupParam({ 'Application Load Balancer Settings': [certificateArnParam] });

    this._keycloakSettings.certificateArn = certificateArnParam.valueAsString;

    if (!props.auroraServerless) {
      const databaseInstanceType = this.makeParam('DatabaseInstanceType', {
        type: 'String',
        description: 'Instance type to be used for the core instances',
        allowedValues: INSTANCE_TYPES,
        default: 'r5.large',
      });
      this.addGroupParam({ 'Database Instance Settings': [databaseInstanceType] });
      this._keycloakSettings.databaseInstanceType = new ec2.InstanceType(databaseInstanceType.valueAsString);
    }

    if (props.fromExistingVPC) {
      const vpcIdParam = this.makeParam('VpcId', {
        type: 'AWS::EC2::VPC::Id',
        description: 'Your VPC Id',
      });
      const pubSubnetsParam = this.makeParam('PubSubnets', {
        type: 'List<AWS::EC2::Subnet::Id>',
        description: 'Public subnets (Choose two)',
      });
      const privSubnetsParam = this.makeParam('PrivSubnets', {
        type: 'List<AWS::EC2::Subnet::Id>',
        description: 'Private subnets (Choose two)',
      });
      const dbSubnetsParam = this.makeParam('DBSubnets', {
        type: 'List<AWS::EC2::Subnet::Id>',
        description: 'Database subnets (Choose two)',
      });
      this.addGroupParam({ 'VPC Settings': [vpcIdParam, pubSubnetsParam, privSubnetsParam, dbSubnetsParam] });

      const azs = ['a', 'b'];
      const vpc = ec2.Vpc.fromVpcAttributes(this, 'VpcAttr', {
        vpcId: vpcIdParam.valueAsString,
        vpcCidrBlock: Aws.NO_VALUE,
        availabilityZones: azs,
        publicSubnetIds: azs.map((_, index) => Fn.select(index, pubSubnetsParam.valueAsList)),
        privateSubnetIds: azs.map((_, index) => Fn.select(index, privSubnetsParam.valueAsList)),
        isolatedSubnetIds: azs.map((_, index) => Fn.select(index, dbSubnetsParam.valueAsList)),
      });

      Object.assign(this._keycloakSettings, {
        vpc,
        publicSubnets: { subnets: vpc.publicSubnets },
        privateSubnets: { subnets: vpc.privateSubnets },
        databaseSubnets: { subnets: vpc.isolatedSubnets },
      });
    }

    const minContainersParam = this.makeParam('MinContainers', {
      type: 'Number',
      description: 'minimum containers count',
      default: 2,
      minValue: 2,
    });
    const maxContainersParam = this.makeParam('MaxContainers', {
      type: 'Number',
      description: 'maximum containers count',
      default: 10,
      minValue: 2,
    });
    const targetCpuUtilizationParam = this.makeParam('AutoScalingTargetCpuUtilization', {
      type: 'Number',
      description: 'Auto scaling target cpu utilization',
      default: 75,
      minValue: 0,
    });
    this.addGroupParam({ 'AutoScaling Settings': [minContainersParam, maxContainersParam, targetCpuUtilizationParam] });

    const javaOptsParam = this.makeParam('JavaOpts', {
      type: 'String',
      description: 'JAVA_OPTS environment variable',
    });
    this.addGroupParam({ 'Environment variable': [javaOptsParam] });

    new KeyCloak(this, 'KeyCloak', {
      vpc: this._keycloakSettings.vpc,
      publicSubnets: this._keycloakSettings.publicSubnets,
      privateSubnets: this._keycloakSettings.privateSubnets,
      databaseSubnets: this._keycloakSettings.databaseSubnets,
      certificateArn: this._keycloakSettings.certificateArn,
      auroraServerless: props.auroraServerless,
      databaseInstanceType: this._keycloakSettings.databaseInstanceType,
      stickinessCookieDuration: Duration.days(7),
      nodeCount: minContainersParam.valueAsNumber,
      autoScaleTask: {
        min: minContainersParam.valueAsNumber,
        max: maxContainersParam.valueAsNumber,
        targetCpuUtilization: targetCpuUtilizationParam.valueAsNumber,
      },
      env: {
        JAVA_OPTS: javaOptsParam.valueAsString,
      },
    });
  }
Example #21
Source File: webAppCloudFrontDistribution.ts    From aws-boilerplate with MIT License 4 votes vote down vote up
export class WebAppCloudFrontDistribution extends Construct {
    private distribution: CloudFrontWebDistribution;

    constructor(scope: Construct, id: string, props: WebAppCloudFrontDistributionProps) {
        super(scope, id);

        const staticFilesBucket = this.createStaticFilesBucket();

        this.distribution = this.createCloudFrontWebDistribution(staticFilesBucket, props);
        this.createDnsRecord(this.distribution, props);
        this.createDeployment(staticFilesBucket, this.distribution, props);
    }

    private createDeployment(staticFilesBucket: Bucket, distribution: CloudFrontWebDistribution, props: WebAppCloudFrontDistributionProps) {
        new BucketDeployment(this, 'DeployWebsite', {
            distribution,
            distributionPaths: ['/index.html'],
            sources: props.sources,
            destinationBucket: staticFilesBucket,
            cacheControl: [CacheControl.setPublic(), CacheControl.maxAge(Duration.hours(1))],
        });
    }

    protected createStaticFilesBucket() {
        return new Bucket(this, "StaticFilesBucket", {
            versioned: true,
            publicReadAccess: true,
            websiteIndexDocument: 'index.html',
        });
    }

    private createCloudFrontWebDistribution(staticFilesBucket: Bucket, props: WebAppCloudFrontDistributionProps) {
        const indexFile = '/index.html';

        const originConfigs = [this.createStaticFilesSourceConfig(staticFilesBucket, props)];
        const apiSourceConfig = this.createApiProxySourceConfig(props);
        if (apiSourceConfig) {
            originConfigs.push(apiSourceConfig);
        }

        return new CloudFrontWebDistribution(this, "CloudFrontWebDistribution", {
            defaultRootObject: indexFile,
            errorConfigurations: [{errorCode: 404, responseCode: 200, responsePagePath: indexFile}],
            originConfigs: originConfigs,
            viewerCertificate: {
                aliases: [props.domainName],
                props: {
                    acmCertificateArn: props.certificateArn,
                    sslSupportMethod: 'sni-only',
                },
            },
        });
    }


    private createStaticFilesSourceConfig(staticFilesBucket: Bucket, props: WebAppCloudFrontDistributionProps): SourceConfiguration {
        const lambdaFunctionAssociations: LambdaFunctionAssociation[] = [];
        const originHeaders: { [key: string]: string } = {};

        if (props.basicAuth) {
            const authLambdaParam = new AwsCustomResource(
                this,
                "GetParameter",
                {
                    policy: AwsCustomResourcePolicy.fromSdkCalls({
                        resources: AwsCustomResourcePolicy.ANY_RESOURCE,
                    }),
                    onUpdate: {
                        action: 'getParameter',
                        parameters: {Name: props.authLambdaSSMParameterName},
                        region: 'us-east-1',
                        service: 'SSM',
                        physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
                    }
                }
            )

            lambdaFunctionAssociations.push({
                eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
                lambdaFunction: lambda.Version.fromVersionArn(this, "AuthLambdaFunction",
                    authLambdaParam.getResponseField("Parameter.Value"))
            });
            originHeaders['X-Auth-String'] = new Buffer(props.basicAuth).toString('base64')
        }

        return {
            behaviors: [{
                lambdaFunctionAssociations,
                isDefaultBehavior: true,
                forwardedValues: {
                    headers: ['Authorization', 'CloudFront-Viewer-Country'],
                    queryString: true,
                }
            }],
            originPath: '',
            customOriginSource: {
                domainName: staticFilesBucket.bucketWebsiteDomainName,
                originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY,
            },
            originHeaders,
        };
    }

    private createApiProxySourceConfig(props: WebAppCloudFrontDistributionProps): SourceConfiguration | null {
        if (!props.apiDomainName) {
            return null;
        }

        return {
            behaviors: [{
                pathPattern: '/api/*',
                allowedMethods: CloudFrontAllowedMethods.ALL,
                forwardedValues: {
                    queryString: true,
                    headers: ["Host"],
                    cookies: {forward: 'all'},
                },
                defaultTtl: Duration.seconds(0),
                minTtl: Duration.seconds(0),
                maxTtl: Duration.seconds(0),
            }],
            customOriginSource: {
                domainName: props.apiDomainName,
                originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
            },
        };
    }

    private createDnsRecord(distribution: CloudFrontWebDistribution, props: WebAppCloudFrontDistributionProps) {
        return new ARecord(this, `DNSRecord`, {
            zone: props.domainZone,
            recordName: props.domainName,
            target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),
        });
    }
}