constructor()

in src/constructs/autoscaling/asg.ts [84:185]


  constructor(scope: GuStack, id: string, props: GuAutoScalingGroupProps) {
    const {
      app,
      additionalSecurityGroups = [],
      blockDevices,
      imageId = new GuAmiParameter(scope, { app }),
      imageRecipe,
      instanceType,
      groupMetrics = [GroupMetrics.all()],
      minimumInstances,
      maximumInstances,
      role = new GuInstanceRole(scope, { app }),
      targetGroup,
      userData,
      vpc,
      httpPutResponseHopLimit,
      updatePolicy,
      enabledDetailedInstanceMonitoring,
      defaultInstanceWarmup,
    } = props;

    // Ensure min and max are defined in the same way. Throwing an `Error` when necessary. For example when min is defined via a Mapping, but max is not.
    if (Token.isUnresolved(minimumInstances) && !Token.isUnresolved(maximumInstances)) {
      throw new Error(
        "minimumInstances is defined via a Mapping, but maximumInstances is not. Create maximumInstances via a Mapping too.",
      );
    }

    // Generate an ID unique to this app
    const launchTemplateId = `${scope.stack}-${scope.stage}-${app}`;
    const launchTemplate = new LaunchTemplate(scope, launchTemplateId, {
      blockDevices,
      detailedMonitoring: enabledDetailedInstanceMonitoring,
      instanceType,
      machineImage: {
        getImage: (): MachineImageConfig => {
          return {
            osType: OperatingSystemType.LINUX,
            userData,
            imageId: imageId.valueAsString,
          };
        },
      },
      // Do not use the default AWS security group which allows egress on any port.
      // Favour HTTPS only egress rules by default.
      securityGroup: GuHttpsEgressSecurityGroup.forVpc(scope, { app, vpc }),

      /*
      Ensure we satisfy FSBP EC2.8 control. If needed, an escape hatch can override this.
      See https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-8.
       */
      requireImdsv2: true,

      instanceMetadataTags: true,
      userData,
      role,
      httpPutResponseHopLimit,
    });

    // Add additional consumer specified Security Groups
    // Note: Launch templates via CDK allow specifying only one SG, so use connections
    // https://github.com/aws/aws-cdk/issues/18712
    additionalSecurityGroups.forEach((sg) => launchTemplate.connections.addSecurityGroup(sg));

    const asgProps: AutoScalingGroupProps = {
      ...props,
      launchTemplate,
      maxCapacity: maximumInstances ?? minimumInstances * 2,
      minCapacity: minimumInstances,
      groupMetrics: groupMetrics,

      // Omit userData, instanceType, blockDevices & role from asgProps
      // As this are specified by the LaunchTemplate and must not be duplicated
      blockDevices: undefined,
      instanceType: undefined,
      role: undefined,
      userData: undefined,
      defaultInstanceWarmup,
    };

    super(scope, id, asgProps);

    this.app = app;
    this.amiParameter = imageId;
    this.imageRecipe = imageRecipe;
    this.instanceLaunchTemplate = launchTemplate;

    if (targetGroup) {
      this.attachToApplicationTargetGroup(targetGroup);
    }

    const cfnAsg = this.node.defaultChild as CfnAutoScalingGroup;

    // A CDK AutoScalingGroup comes with this update policy, whereas the CFN autscaling group
    // leaves it to the default value, which is actually false.
    // { UpdatePolicy: { autoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true }}
    if (!updatePolicy) {
      cfnAsg.addDeletionOverride("UpdatePolicy");
    }

    Tags.of(launchTemplate).add("App", app);
  }