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() });
}