in packages/aws-cdk-lib/aws-ec2/lib/launch-template.ts [608:846]
constructor(scope: Construct, id: string, props: LaunchTemplateProps = {}) {
super(scope, id);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
// Basic validation of the provided spot block duration
const spotDuration = props?.spotOptions?.blockDuration?.toHours({ integral: true });
if (spotDuration !== undefined && (spotDuration < 1 || spotDuration > 6)) {
// See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-requests.html#fixed-duration-spot-instances
Annotations.of(this).addError('Spot block duration must be exactly 1, 2, 3, 4, 5, or 6 hours.');
}
// Basic validation of the provided httpPutResponseHopLimit
if (props.httpPutResponseHopLimit !== undefined && (props.httpPutResponseHopLimit < 1 || props.httpPutResponseHopLimit > 64)) {
// See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-metadataoptions.html#cfn-ec2-launchtemplate-launchtemplatedata-metadataoptions-httpputresponsehoplimit
Annotations.of(this).addError('HttpPutResponseHopLimit must between 1 and 64');
}
if (props.instanceProfile && props.role) {
throw new ValidationError('You cannot provide both an instanceProfile and a role', this);
}
if (props.keyName && props.keyPair) {
throw new ValidationError('Cannot specify both of \'keyName\' and \'keyPair\'; prefer \'keyPair\'', this);
}
// use provided instance profile or create one if a role was provided
let iamProfileArn: string | undefined = undefined;
if (props.instanceProfile) {
this.role = props.instanceProfile.role;
iamProfileArn = props.instanceProfile.instanceProfileArn;
} else if (props.role) {
this.role = props.role;
const iamProfile = new iam.CfnInstanceProfile(this, 'Profile', {
roles: [this.role.roleName],
});
iamProfileArn = iamProfile.attrArn;
}
this._grantPrincipal = this.role;
if (props.securityGroup) {
this._connections = new Connections({ securityGroups: [props.securityGroup] });
}
const securityGroupsToken = Lazy.list({
produce: () => {
if (this._connections && this._connections.securityGroups.length > 0) {
return this._connections.securityGroups.map(sg => sg.securityGroupId);
}
return undefined;
},
});
const imageConfig: MachineImageConfig | undefined = props.machineImage?.getImage(this);
if (imageConfig) {
this.osType = imageConfig.osType;
this.imageId = imageConfig.imageId;
}
if (this.osType && props.keyPair && !props.keyPair._isOsCompatible(this.osType)) {
throw new ValidationError(`${props.keyPair.type} keys are not compatible with the chosen AMI`, this);
}
if (FeatureFlags.of(this).isEnabled(cxapi.EC2_LAUNCH_TEMPLATE_DEFAULT_USER_DATA) ||
FeatureFlags.of(this).isEnabled(cxapi.AUTOSCALING_GENERATE_LAUNCH_TEMPLATE)) {
// priority: prop.userData -> userData from machineImage -> undefined
this.userData = props.userData ?? imageConfig?.userData;
} else {
if (props.userData) {
this.userData = props.userData;
}
}
const userDataToken = Lazy.string({
produce: () => {
if (this.userData) {
return Fn.base64(this.userData.render());
}
return undefined;
},
});
this.instanceType = props.instanceType;
let marketOptions: any = undefined;
if (props?.spotOptions) {
marketOptions = {
marketType: 'spot',
spotOptions: {
blockDurationMinutes: spotDuration !== undefined ? spotDuration * 60 : undefined,
instanceInterruptionBehavior: props.spotOptions.interruptionBehavior,
maxPrice: props.spotOptions.maxPrice?.toString(),
spotInstanceType: props.spotOptions.requestType,
validUntil: props.spotOptions.validUntil?.date.toUTCString(),
},
};
// Remove SpotOptions if there are none.
if (Object.keys(marketOptions.spotOptions).filter(k => marketOptions.spotOptions[k]).length == 0) {
marketOptions.spotOptions = undefined;
}
}
this.tags = new TagManager(TagType.KEY_VALUE, 'AWS::EC2::LaunchTemplate');
const tagsToken = Lazy.any({
produce: () => {
if (this.tags.hasTags()) {
const renderedTags = this.tags.renderTags();
const lowerCaseRenderedTags = renderedTags.map( (tag: { [key: string]: string}) => {
return {
key: tag.Key,
value: tag.Value,
};
});
return [
{
resourceType: 'instance',
tags: lowerCaseRenderedTags,
},
{
resourceType: 'volume',
tags: lowerCaseRenderedTags,
},
];
}
return undefined;
},
});
const ltTagsToken = Lazy.any({
produce: () => {
if (this.tags.hasTags()) {
const renderedTags = this.tags.renderTags();
const lowerCaseRenderedTags = renderedTags.map( (tag: { [key: string]: string}) => {
return {
key: tag.Key,
value: tag.Value,
};
});
return [
{
resourceType: 'launch-template',
tags: lowerCaseRenderedTags,
},
];
}
return undefined;
},
});
const networkInterfaces = props.associatePublicIpAddress !== undefined
? [{ deviceIndex: 0, associatePublicIpAddress: props.associatePublicIpAddress, groups: securityGroupsToken }]
: undefined;
if (props.versionDescription && !Token.isUnresolved(props.versionDescription) && props.versionDescription.length > 255) {
throw new ValidationError(`versionDescription must be less than or equal to 255 characters, got ${props.versionDescription.length}`, this);
}
const resource = new CfnLaunchTemplate(this, 'Resource', {
launchTemplateName: props?.launchTemplateName,
versionDescription: props?.versionDescription,
launchTemplateData: {
blockDeviceMappings: props?.blockDevices !== undefined ? launchTemplateBlockDeviceMappings(this, props.blockDevices) : undefined,
creditSpecification: props?.cpuCredits !== undefined ? {
cpuCredits: props.cpuCredits,
} : undefined,
disableApiTermination: props?.disableApiTermination,
ebsOptimized: props?.ebsOptimized,
enclaveOptions: props?.nitroEnclaveEnabled !== undefined ? {
enabled: props.nitroEnclaveEnabled,
} : undefined,
hibernationOptions: props?.hibernationConfigured !== undefined ? {
configured: props.hibernationConfigured,
} : undefined,
iamInstanceProfile: iamProfileArn !== undefined ? { arn: iamProfileArn } : undefined,
imageId: imageConfig?.imageId,
instanceType: props?.instanceType?.toString(),
instanceInitiatedShutdownBehavior: props?.instanceInitiatedShutdownBehavior,
instanceMarketOptions: marketOptions,
keyName: props.keyPair?.keyPairName ?? props?.keyName,
monitoring: props?.detailedMonitoring !== undefined ? {
enabled: props.detailedMonitoring,
} : undefined,
securityGroupIds: networkInterfaces ? undefined : securityGroupsToken,
tagSpecifications: tagsToken,
userData: userDataToken,
metadataOptions: this.renderMetadataOptions(props),
networkInterfaces,
placement: props.placementGroup ? {
groupName: props.placementGroup.placementGroupName,
} : undefined,
// Fields not yet implemented:
// ==========================
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-capacityreservationspecification.html
// Will require creating an L2 for AWS::EC2::CapacityReservation
// capacityReservationSpecification: undefined,
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-cpuoptions.html
// cpuOptions: undefined,
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-elasticgpuspecification.html
// elasticGpuSpecifications: undefined,
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-elasticinferenceaccelerators
// elasticInferenceAccelerators: undefined,
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-kernelid
// kernelId: undefined,
// ramDiskId: undefined,
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-licensespecifications
// Also not implemented in Instance L2
// licenseSpecifications: undefined,
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-tagspecifications
// Should be implemented via the Tagging aspect in CDK core. Complication will be that this tagging interface is very unique to LaunchTemplates.
// tagSpecification: undefined
// CDK only has placement groups, not placement.
// Specifiying options other than placementGroup is not supported yet.
// placement: undefined,
},
tagSpecifications: ltTagsToken,
});
if (this.role) {
resource.node.addDependency(this.role);
} else if (props.instanceProfile?.role) {
resource.node.addDependency(props.instanceProfile.role);
}
Tags.of(this).add(NAME_TAG, this.node.path);
this.defaultVersionNumber = resource.attrDefaultVersionNumber;
this.latestVersionNumber = resource.attrLatestVersionNumber;
this.launchTemplateId = resource.ref;
this.versionNumber = Token.asString(resource.getAtt('LatestVersionNumber'));
}