constructor()

in src/ec2-deployer.ts [99:194]


  constructor(scope: cdk.Construct, id: string, props: Ec2DeployerProps) {
    // Validate that props.deploymentTimeout is less than 2 hours, per maximum value accepted by downstream customresources.Provider.totalTimeout
    if (props.deploymentTimeout && props.deploymentTimeout.toMilliseconds() > Ec2Deployer.MAX_DEPLOYMENT_TIMEOUT.toMilliseconds()) { // have to convert to milliseconds in case the cdk.Duration is passed in milliseconds
      throw new Error(`Invalid prop: deploymentTimeout must be less than ${Ec2Deployer.MAX_DEPLOYMENT_TIMEOUT.toHumanString()}.`);
    }

    // Validate that at least one instanceRole is supplied if we cannot get them from deploymentGroup.autoScalingGroups
    if (!props.deploymentGroup.autoScalingGroups && (!props.instanceRoles || props.instanceRoles.length === 0)) {
      throw new Error('If deploymentGroup is of type IServerDeploymentGroup, you must supply at least one role in instanceRoles.');
    }

    super(scope, id);

    // Set defaults for any missing props
    this.code = props.code.bind(this);
    this.deploymentGroup = props.deploymentGroup;
    this.waitToComplete = props.waitToComplete !== undefined ? props.waitToComplete : true;
    this.deploymentTimeout = this.waitToComplete ? props.deploymentTimeout || cdk.Duration.minutes(5) : undefined; // can only be defined if waitToComplete=true because of downstream customresources.Provider.totalTimeout

    // Create OnEventHandler Lambda function for custom resource
    // Can't use SingletonFunction because permissions are dependent on props passed into each Ec2Deployer instance
    const onEvent = new lambda.Function(this, 'OnEventHandler', {
    // const onEvent = new lambda.SingletonFunction(this, 'OnEventHandler', {
    //   uuid: '3a9c56a9-1dd5-42dc-af2f-10b76edde830',
      code: lambda.Code.fromAsset(path.join(__dirname, '../custom-resource-runtime/ec2-deployer')),
      runtime: lambda.Runtime.PYTHON_3_8,
      handler: 'index.on_event',
      initialPolicy: [
        new iam.PolicyStatement({
          actions: ['codedeploy:GetDeploymentConfig'],
          resources: [codedeploy.ServerDeploymentConfig.ONE_AT_A_TIME.deploymentConfigArn],
        }),
        new iam.PolicyStatement({
          actions: ['codedeploy:CreateDeployment'],
          resources: [this.deploymentGroup.deploymentGroupArn],
        }),
        new iam.PolicyStatement({
          actions: ['codedeploy:GetApplicationRevision', 'codedeploy:RegisterApplicationRevision'],
          resources: [this.deploymentGroup.application.applicationArn],
        }),
      ],
    });

    // Create IsCompleteHandler Lambda function for custom resource, only if waitToComplete=true
    // Can't use SingletonFunction because permissions are dependent on props passed into each Ec2Deployer instance
    let isComplete = undefined;
    if (this.waitToComplete) {
      // isComplete = new lambda.SingletonFunction(this, 'IsCompleteHandler', {
      //   uuid: 'f58e4e2e-8b7e-4bd0-b33b-c5c9f19f5546',
      isComplete = new lambda.Function(this, 'IsCompleteHandler', {
        code: lambda.Code.fromAsset(path.join(__dirname, '../custom-resource-runtime/ec2-deployer')),
        runtime: lambda.Runtime.PYTHON_3_8,
        handler: 'index.is_complete',
        initialPolicy: [
          new iam.PolicyStatement({
            resources: [this.deploymentGroup.deploymentGroupArn],
            actions: ['codedeploy:GetDeployment'],
          }),
        ],
      });
    }

    // Create provider for custom resource
    const deployerProvider = new customresources.Provider(this, 'Provider', {
      onEventHandler: onEvent,
      totalTimeout: this.deploymentTimeout,
      isCompleteHandler: isComplete,
    });

    // Ensure ASGs have read access to code S3 object for deployment
    const policyStatement = new iam.PolicyStatement({
      actions: ['s3:GetObject*'],
      resources: [`arn:${cdk.Stack.of(this).partition}:s3:::${this.code.s3Location.bucketName}/${this.code.s3Location.objectKey}`],
    });
    if (props.instanceRoles) {
      for (let role of props.instanceRoles) {
        role.addToPrincipalPolicy(policyStatement);
      }
    } else {
      for (let asg of this.deploymentGroup.autoScalingGroups!) {
        (asg as autoscaling.AutoScalingGroup).role.addToPrincipalPolicy(policyStatement);
      }
    }

    // Create custom resource that triggers a deployment
    new cdk.CustomResource(this, 'CustomResource', {
      serviceToken: deployerProvider.serviceToken,
      properties: {
        applicationName: this.deploymentGroup.application.applicationName,
        deploymentGroupName: this.deploymentGroup.deploymentGroupName,
        codeS3BucketName: this.code.s3Location.bucketName,
        codeS3ObjectKey: this.code.s3Location.objectKey,
        codeS3ObjectVersion: this.code.s3Location.objectVersion,
      },
    });
  }