constructor()

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