constructor()

in packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts [1343:1617]


  constructor(scope: Construct, id: string, props: AutoScalingGroupProps) {
    super(scope, id, {
      physicalName: props.autoScalingGroupName,
    });
    // Enhanced CDK Analytics Telemetry
    addConstructMetadata(this, props);

    this.newInstancesProtectedFromScaleIn = props.newInstancesProtectedFromScaleIn;

    if (props.initOptions && !props.init) {
      throw new ValidationError('Setting \'initOptions\' requires that \'init\' is also set', this);
    }

    if (props.groupMetrics) {
      this.groupMetrics.push(...props.groupMetrics);
    }

    let launchConfig: CfnLaunchConfiguration | undefined = undefined;
    let launchTemplateFromConfig: ec2.LaunchTemplate | undefined = undefined;
    if (props.launchTemplate || props.mixedInstancesPolicy) {
      this.verifyNoLaunchConfigPropIsGiven(props);

      const bareLaunchTemplate = props.launchTemplate;
      const mixedInstancesPolicy = props.mixedInstancesPolicy;

      if (bareLaunchTemplate && mixedInstancesPolicy) {
        throw new ValidationError('Setting \'mixedInstancesPolicy\' must not be set when \'launchTemplate\' is set', this);
      }

      if (bareLaunchTemplate && bareLaunchTemplate instanceof ec2.LaunchTemplate) {
        if (!bareLaunchTemplate.instanceType) {
          throw new ValidationError('Setting \'launchTemplate\' requires its \'instanceType\' to be set', this);
        }

        if (!bareLaunchTemplate.imageId) {
          throw new ValidationError('Setting \'launchTemplate\' requires its \'machineImage\' to be set', this);
        }

        this.launchTemplate = bareLaunchTemplate;
      }

      if (mixedInstancesPolicy && mixedInstancesPolicy.launchTemplate instanceof ec2.LaunchTemplate) {
        if (!mixedInstancesPolicy.launchTemplate.imageId) {
          throw new ValidationError('Setting \'mixedInstancesPolicy.launchTemplate\' requires its \'machineImage\' to be set', this);
        }

        this.launchTemplate = mixedInstancesPolicy.launchTemplate;
      }

      this._role = this.launchTemplate?.role;
      this.grantPrincipal = this._role || new iam.UnknownPrincipal({ resource: this });

      this.osType = this.launchTemplate?.osType ?? ec2.OperatingSystemType.UNKNOWN;
    } else {
      if (!props.machineImage) {
        throw new ValidationError('Setting \'machineImage\' is required when \'launchTemplate\' and \'mixedInstancesPolicy\' is not set', this);
      }
      if (!props.instanceType) {
        throw new ValidationError('Setting \'instanceType\' is required when \'launchTemplate\' and \'mixedInstancesPolicy\' is not set', this);
      }

      if (props.keyName && props.keyPair) {
        throw new ValidationError('Cannot specify both of \'keyName\' and \'keyPair\'; prefer \'keyPair\'', this);
      }

      Tags.of(this).add(NAME_TAG, this.node.path);

      this.securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'InstanceSecurityGroup', {
        vpc: props.vpc,
        allowAllOutbound: props.allowAllOutbound !== false,
      });

      this._role = props.role || new iam.Role(this, 'InstanceRole', {
        roleName: PhysicalName.GENERATE_IF_NEEDED,
        assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      });
      this.grantPrincipal = this._role;

      const iamProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
        roles: [this.role.roleName],
      });

      // generate launch template from launch config props when feature flag is set
      if (FeatureFlags.of(this).isEnabled(AUTOSCALING_GENERATE_LAUNCH_TEMPLATE)) {
        const instanceProfile = iam.InstanceProfile.fromInstanceProfileAttributes(this, 'ImportedInstanceProfile', {
          instanceProfileArn: iamProfile.attrArn,
          role: this.role,
        });

        launchTemplateFromConfig = new ec2.LaunchTemplate(this, 'LaunchTemplate', {
          machineImage: props.machineImage,
          instanceType: props.instanceType,
          detailedMonitoring: props.instanceMonitoring !== undefined && props.instanceMonitoring === Monitoring.DETAILED,
          securityGroup: this.securityGroup,
          userData: props.userData,
          associatePublicIpAddress: props.associatePublicIpAddress,
          spotOptions: props.spotPrice !== undefined ? { maxPrice: parseFloat(props.spotPrice) } : undefined,
          blockDevices: props.blockDevices,
          instanceProfile,
          keyPair: props.keyPair,
          ...(props.keyName ? { keyName: props.keyName } : {}),
        });

        this.osType = launchTemplateFromConfig.osType!;
        this.launchTemplate = launchTemplateFromConfig;
      } else {
        this._connections = new ec2.Connections({ securityGroups: [this.securityGroup] });
        this.securityGroups = [this.securityGroup];

        if (props.keyPair) {
          throw new ValidationError('Can only use \'keyPair\' when feature flag \'AUTOSCALING_GENERATE_LAUNCH_TEMPLATE\' is set', this);
        }

        // use delayed evaluation
        const imageConfig = props.machineImage.getImage(this);
        this._userData = props.userData ?? imageConfig.userData;
        const userDataToken = Lazy.string({ produce: () => Fn.base64(this.userData!.render()) });
        const securityGroupsToken = Lazy.list({ produce: () => this.securityGroups!.map(sg => sg.securityGroupId) });

        launchConfig = new CfnLaunchConfiguration(this, 'LaunchConfig', {
          imageId: imageConfig.imageId,
          keyName: props.keyName,
          instanceType: props.instanceType.toString(),
          instanceMonitoring: (props.instanceMonitoring !== undefined ? (props.instanceMonitoring === Monitoring.DETAILED) : undefined),
          securityGroups: securityGroupsToken,
          iamInstanceProfile: iamProfile.ref,
          userData: userDataToken,
          associatePublicIpAddress: props.associatePublicIpAddress,
          spotPrice: props.spotPrice,
          blockDeviceMappings: (props.blockDevices !== undefined ?
            synthesizeBlockDeviceMappings(this, props.blockDevices) : undefined),
        });

        launchConfig.node.addDependency(this.role);
        this.osType = imageConfig.osType;
      }
    }

    if (props.ssmSessionPermissions && this._role) {
      this._role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
    }

    // desiredCapacity just reflects what the user has supplied.
    const desiredCapacity = props.desiredCapacity;
    const minCapacity = props.minCapacity ?? 1;
    const maxCapacity = props.maxCapacity ??
      desiredCapacity ??
      (Token.isUnresolved(minCapacity) ? minCapacity : Math.max(minCapacity, 1));

    withResolved(minCapacity, maxCapacity, (min, max) => {
      if (min > max) {
        throw new ValidationError(`minCapacity (${min}) should be <= maxCapacity (${max})`, this);
      }
    });
    withResolved(desiredCapacity, minCapacity, (desired, min) => {
      if (desired === undefined) { return; }
      if (desired < min) {
        throw new ValidationError(`Should have minCapacity (${min}) <= desiredCapacity (${desired})`, this);
      }
    });
    withResolved(desiredCapacity, maxCapacity, (desired, max) => {
      if (desired === undefined) { return; }
      if (max < desired) {
        throw new ValidationError(`Should have desiredCapacity (${desired}) <= maxCapacity (${max})`, this);
      }
    });

    if (desiredCapacity !== undefined) {
      Annotations.of(this).addWarningV2('@aws-cdk/aws-autoscaling:desiredCapacitySet', 'desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215');
    }

    this.maxInstanceLifetime = props.maxInstanceLifetime;
    // See https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-max-instance-lifetime.html for details on max instance lifetime.
    if (this.maxInstanceLifetime && !this.maxInstanceLifetime.isUnresolved() &&
      (this.maxInstanceLifetime.toSeconds() !== 0) &&
      (this.maxInstanceLifetime.toSeconds() < 86400 || this.maxInstanceLifetime.toSeconds() > 31536000)) {
      throw new ValidationError('maxInstanceLifetime must be between 1 and 365 days (inclusive)', this);
    }

    if (props.notificationsTopic && props.notifications) {
      throw new ValidationError('Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead', this);
    }

    if (props.notificationsTopic) {
      this.notifications = [{
        topic: props.notificationsTopic,
      }];
    }

    if (props.notifications) {
      this.notifications = props.notifications.map(nc => ({
        topic: nc.topic,
        scalingEvents: nc.scalingEvents ?? ScalingEvents.ALL,
      }));
    }

    const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets);

    const terminationPolicies: string[] = [];
    if (props.terminationPolicies) {
      props.terminationPolicies.forEach((terminationPolicy, index) => {
        if (terminationPolicy === TerminationPolicy.CUSTOM_LAMBDA_FUNCTION) {
          if (index !== 0) {
            throw new ValidationError('TerminationPolicy.CUSTOM_LAMBDA_FUNCTION must be specified first in the termination policies', this);
          }

          if (!props.terminationPolicyCustomLambdaFunctionArn) {
            throw new ValidationError('terminationPolicyCustomLambdaFunctionArn property must be specified if the TerminationPolicy.CUSTOM_LAMBDA_FUNCTION is used', this);
          }

          terminationPolicies.push(props.terminationPolicyCustomLambdaFunctionArn);
        } else {
          terminationPolicies.push(terminationPolicy);
        }
      });
    }

    const { healthCheckType, healthCheckGracePeriod } = this.renderHealthChecks(props.healthChecks, props.healthCheck);

    const asgProps: CfnAutoScalingGroupProps = {
      autoScalingGroupName: this.physicalName,
      availabilityZoneDistribution: props.azCapacityDistributionStrategy
        ? { capacityDistributionStrategy: props.azCapacityDistributionStrategy }
        : undefined,
      cooldown: props.cooldown?.toSeconds().toString(),
      minSize: Tokenization.stringifyNumber(minCapacity),
      maxSize: Tokenization.stringifyNumber(maxCapacity),
      desiredCapacity: desiredCapacity !== undefined ? Tokenization.stringifyNumber(desiredCapacity) : undefined,
      loadBalancerNames: Lazy.list({ produce: () => this.loadBalancerNames }, { omitEmpty: true }),
      targetGroupArns: Lazy.list({ produce: () => this.targetGroupArns }, { omitEmpty: true }),
      notificationConfigurations: this.renderNotificationConfiguration(),
      metricsCollection: Lazy.any({ produce: () => this.renderMetricsCollection() }),
      vpcZoneIdentifier: subnetIds,
      healthCheckType,
      healthCheckGracePeriod,
      maxInstanceLifetime: this.maxInstanceLifetime ? this.maxInstanceLifetime.toSeconds() : undefined,
      newInstancesProtectedFromScaleIn: Lazy.any({ produce: () => this.newInstancesProtectedFromScaleIn }),
      terminationPolicies: terminationPolicies.length === 0 ? undefined : terminationPolicies,
      defaultInstanceWarmup: props.defaultInstanceWarmup?.toSeconds(),
      capacityRebalance: props.capacityRebalance,
      instanceMaintenancePolicy: this.renderInstanceMaintenancePolicy(
        props.minHealthyPercentage,
        props.maxHealthyPercentage,
      ),
      ...this.getLaunchSettings(launchConfig, props.launchTemplate ?? launchTemplateFromConfig, props.mixedInstancesPolicy),
    };

    if (!hasPublic && props.associatePublicIpAddress) {
      throw new ValidationError("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.PUBLIC })", this);
    }

    this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps);
    this.autoScalingGroupName = this.getResourceNameAttribute(this.autoScalingGroup.ref),
    this.autoScalingGroupArn = Stack.of(this).formatArn({
      service: 'autoscaling',
      resource: 'autoScalingGroup:*:autoScalingGroupName',
      resourceName: this.autoScalingGroupName,
    });
    this.node.defaultChild = this.autoScalingGroup;

    this.applyUpdatePolicies(props, { desiredCapacity, minCapacity });
    if (props.init) {
      this.applyCloudFormationInit(props.init, props.initOptions);
    }

    this.spotPrice = props.spotPrice;

    if (props.requireImdsv2) {
      Aspects.of(this).add(new AutoScalingGroupRequireImdsv2Aspect(), {
        priority: mutatingAspectPrio32333(this),
      });
    }

    this.node.addValidation({ validate: () => this.validateTargetGroup() });
  }