in packages/aws-cdk-lib/aws-ec2/lib/instance.ts [464:679]
constructor(scope: Construct, id: string, props: InstanceProps) {
super(scope, id);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
if (props.initOptions && !props.init) {
throw new ValidationError('Setting \'initOptions\' requires that \'init\' is also set', this);
}
if (props.keyName && props.keyPair) {
throw new ValidationError('Cannot specify both of \'keyName\' and \'keyPair\'; prefer \'keyPair\'', this);
}
// if credit specification is set, then the instance type must be burstable
if (props.creditSpecification && !props.instanceType.isBurstable()) {
throw new ValidationError(`creditSpecification is supported only for T4g, T3a, T3, T2 instance type, got: ${props.instanceType.toString()}`, this);
}
if (props.securityGroup) {
this.securityGroup = props.securityGroup;
} else {
this.securityGroup = new SecurityGroup(this, 'InstanceSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: props.allowAllOutbound !== false,
allowAllIpv6Outbound: props.allowAllIpv6Outbound,
});
}
this.connections = new Connections({ securityGroups: [this.securityGroup] });
this.securityGroups.push(this.securityGroup);
Tags.of(this).add(NAME_TAG, props.instanceName || this.node.path);
if (props.instanceProfile && props.role) {
throw new ValidationError('You cannot provide both instanceProfile and role', this);
}
let iamInstanceProfile: string | undefined = undefined;
if (props.instanceProfile?.role) {
this.role = props.instanceProfile.role;
iamInstanceProfile = props.instanceProfile.instanceProfileName;
} else {
this.role = props.role || new iam.Role(this, 'InstanceRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
});
const iamProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
roles: [this.role.roleName],
});
iamInstanceProfile = iamProfile.ref;
}
this.grantPrincipal = this.role;
if (props.ssmSessionPermissions) {
this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
}
// 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) });
const { subnets, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets);
let subnet;
if (props.availabilityZone) {
const selected = subnets.filter(sn => sn.availabilityZone === props.availabilityZone);
if (selected.length === 1) {
subnet = selected[0];
} else {
Annotations.of(this).addError(`Need exactly 1 subnet to match AZ '${props.availabilityZone}', found ${selected.length}. Use a different availabilityZone.`);
}
} else {
if (subnets.length > 0) {
subnet = subnets[0];
} else {
Annotations.of(this).addError(`Did not find any subnets matching '${JSON.stringify(props.vpcSubnets)}', please use a different selection.`);
}
}
if (!subnet) {
// We got here and we don't have a subnet because of validation errors.
// Invent one on the spot so the code below doesn't fail.
subnet = Subnet.fromSubnetAttributes(this, 'DummySubnet', {
subnetId: 's-notfound',
availabilityZone: 'az-notfound',
});
}
// network interfaces array is set to configure the primary network interface if associatePublicIpAddress is true or false
const networkInterfaces = props.associatePublicIpAddress !== undefined
? [{
deviceIndex: '0',
associatePublicIpAddress: props.associatePublicIpAddress,
subnetId: subnet.subnetId,
groupSet: securityGroupsToken,
privateIpAddress: props.privateIpAddress,
}] : undefined;
if (props.keyPair && !props.keyPair._isOsCompatible(imageConfig.osType)) {
throw new ValidationError(`${props.keyPair.type} keys are not compatible with the chosen AMI`, this);
}
if (props.enclaveEnabled && props.hibernationEnabled) {
throw new ValidationError('You can\'t set both `enclaveEnabled` and `hibernationEnabled` to true on the same instance', this);
}
if (
props.ipv6AddressCount !== undefined &&
!Token.isUnresolved(props.ipv6AddressCount) &&
(props.ipv6AddressCount < 0 || !Number.isInteger(props.ipv6AddressCount))
) {
throw new ValidationError(`\'ipv6AddressCount\' must be a non-negative integer, got: ${props.ipv6AddressCount}`, this);
}
if (
props.ipv6AddressCount !== undefined &&
props.associatePublicIpAddress !== undefined) {
throw new ValidationError('You can\'t set both \'ipv6AddressCount\' and \'associatePublicIpAddress\'', this);
}
// if network interfaces array is configured then subnetId, securityGroupIds,
// and privateIpAddress are configured on the network interface level and
// there is no need to configure them on the instance level
this.instance = new CfnInstance(this, 'Resource', {
imageId: imageConfig.imageId,
keyName: props.keyPair?.keyPairName ?? props?.keyName,
instanceType: props.instanceType.toString(),
subnetId: networkInterfaces ? undefined : subnet.subnetId,
securityGroupIds: networkInterfaces ? undefined : securityGroupsToken,
networkInterfaces,
iamInstanceProfile,
userData: userDataToken,
availabilityZone: subnet.availabilityZone,
sourceDestCheck: props.sourceDestCheck,
blockDeviceMappings: props.blockDevices !== undefined ? instanceBlockDeviceMappings(this, props.blockDevices) : undefined,
privateIpAddress: networkInterfaces ? undefined : props.privateIpAddress,
propagateTagsToVolumeOnCreation: props.propagateTagsToVolumeOnCreation,
monitoring: props.detailedMonitoring,
creditSpecification: props.creditSpecification ? { cpuCredits: props.creditSpecification } : undefined,
ebsOptimized: props.ebsOptimized,
disableApiTermination: props.disableApiTermination,
instanceInitiatedShutdownBehavior: props.instanceInitiatedShutdownBehavior,
placementGroupName: props.placementGroup?.placementGroupName,
enclaveOptions: props.enclaveEnabled !== undefined ? { enabled: props.enclaveEnabled } : undefined,
hibernationOptions: props.hibernationEnabled !== undefined ? { configured: props.hibernationEnabled } : undefined,
ipv6AddressCount: props.ipv6AddressCount,
});
this.instance.node.addDependency(this.role);
// if associatePublicIpAddress is true, then there must be a dependency on internet connectivity
if (props.associatePublicIpAddress !== undefined && props.associatePublicIpAddress) {
const internetConnected = props.vpc.selectSubnets(props.vpcSubnets).internetConnectivityEstablished;
this.instance.node.addDependency(internetConnected);
}
if (!hasPublic && props.associatePublicIpAddress) {
throw new ValidationError("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.PUBLIC })", this);
}
this.osType = imageConfig.osType;
this.node.defaultChild = this.instance;
this.instanceId = this.instance.ref;
this.instanceAvailabilityZone = this.instance.attrAvailabilityZone;
this.instancePrivateDnsName = this.instance.attrPrivateDnsName;
this.instancePrivateIp = this.instance.attrPrivateIp;
this.instancePublicDnsName = this.instance.attrPublicDnsName;
this.instancePublicIp = this.instance.attrPublicIp;
// When feature flag is true, if both the resourceSignalTimeout and initOptions.timeout are set,
// the timeout is summed together. This logic is done in applyCloudFormationInit.
// This is because applyUpdatePolicies overwrites the timeout when both timeout fields are specified.
if (FeatureFlags.of(this).isEnabled(cxapi.EC2_SUM_TIMEOUT_ENABLED)) {
this.applyUpdatePolicies(props);
if (props.init) {
this.applyCloudFormationInit(props.init, props.initOptions);
}
} else {
if (props.init) {
this.applyCloudFormationInit(props.init, props.initOptions);
}
this.applyUpdatePolicies(props);
}
// Trigger replacement (via new logical ID) on user data change, if specified or cfn-init is being used.
//
// This is slightly tricky -- we need to resolve the UserData string (in order to get at actual Asset hashes,
// instead of the Token stringifications of them ('${Token[1234]}'). However, in the case of CFN Init usage,
// a UserData is going to contain the logicalID of the resource itself, which means infinite recursion if we
// try to naively resolve. We need a recursion breaker in this.
const originalLogicalId = Stack.of(this).getLogicalId(this.instance);
let recursing = false;
this.instance.overrideLogicalId(Lazy.uncachedString({
produce: (context) => {
if (recursing) { return originalLogicalId; }
if (!(props.userDataCausesReplacement ?? props.initOptions)) { return originalLogicalId; }
const fragments = new Array<string>();
recursing = true;
try {
fragments.push(JSON.stringify(context.resolve(this.userData.render())));
} finally {
recursing = false;
}
const digest = md5hash(fragments.join('')).slice(0, 16);
return `${originalLogicalId}${digest}`;
},
}));
if (props.requireImdsv2) {
Aspects.of(this).add(new InstanceRequireImdsv2Aspect(), {
priority: mutatingAspectPrio32333(this),
});
}
}