constructor()

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