async function createEc2InstanceWithFleetParams()

in src/aws.js [231:341]


async function createEc2InstanceWithFleetParams(imageId, subnetId, securityGroupId, label, githubRegistrationToken, region) {
  const ec2 = new EC2Client({ region });

  const overrides = (config.input.ec2InstanceTypes || []).map((type) => ({
    InstanceType: type,
    SubnetId: subnetId,
    // For Type='instant', allow AMI override so callers can pass ImageId without baking it into LT
    ...(imageId ? { ImageId: imageId } : {})
  }));

  // Prepare to ensure we have a Launch Template ID (create one if not provided)
  let launchTemplateId = config.input.launchTemplateId;
  let createdTemporaryLt = false;

  if (!launchTemplateId) {
    core.info('No launch template ID provided. Creating a temporary Launch Template...');

    const userData = buildUserDataScript(githubRegistrationToken, label);
    core.info('Executing user data script: ' + userData.replace(githubRegistrationToken, '<redacted>'));

    // Build LaunchTemplateData similar to RunInstances params
    const ltData = {
      SecurityGroupIds: [securityGroupId],
      UserData: Buffer.from(userData).toString('base64'),
      TagSpecifications: config.tagSpecifications
    };

    // Block device mappings (prefer explicit mappings if provided)
    if (config.input.blockDeviceMappings && config.input.blockDeviceMappings.length > 0) {
      ltData.BlockDeviceMappings = config.input.blockDeviceMappings;
    } else if (config.input.ec2VolumeSize !== '' || config.input.ec2VolumeType !== '') {
      ltData.BlockDeviceMappings = [
        {
          DeviceName: config.input.ec2DeviceName,
          Ebs: {
            ...(config.input.ec2VolumeSize !== '' && { VolumeSize: config.input.ec2VolumeSize }),
            ...(config.input.ec2VolumeType !== '' && { VolumeType: config.input.ec2VolumeType })
          }
        }
      ];
    }

    const ltName = `ec2-github-runner-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;

    const createLtParams = {
      LaunchTemplateName: ltName,
      LaunchTemplateData: ltData
    };

    const createLtRes = await ec2.send(new CreateLaunchTemplateCommand(createLtParams));
    launchTemplateId = createLtRes.LaunchTemplate.LaunchTemplateId;
    createdTemporaryLt = true;
    core.info(`Created temporary Launch Template ${launchTemplateId} with name ${ltName}`);
  }

  const isSpot = config.input.marketType === 'spot';

  const fleetParams = {
    Type: 'instant',
    LaunchTemplateConfigs: [
      {
        LaunchTemplateSpecification: {
          LaunchTemplateId: launchTemplateId,
          Version: '$Latest'
        },
        Overrides: overrides
      }
    ],
    TargetCapacitySpecification: {
      TotalTargetCapacity: 1,
      DefaultTargetCapacityType: isSpot ? 'spot' : 'on-demand',
      SpotTargetCapacity: isSpot ? 1 : 0,
      OnDemandTargetCapacity: isSpot ? 0 : 1
    },
    SpotOptions: isSpot ? { AllocationStrategy: 'price-capacity-optimized' } : undefined
  };

  let ec2InstanceId;
  let ec2InstanceType;
  const fleetRes = await ec2.send(new CreateFleetCommand(fleetParams));

  // Try to extract instance ID from the response (Type='instant' should return Instances)
  if (Array.isArray(fleetRes.Instances)) {
    core.info(`EC2 Fleet returned ${fleetRes.Instances.length} instances`);
    for (const group of fleetRes.Instances) {
      if (Array.isArray(group.InstanceIds) && group.InstanceIds.length > 0) {
        ec2InstanceId = group.InstanceIds[0];
        ec2InstanceType = group.InstanceType;
        break;
      }
    }
  }

  if (!ec2InstanceId) {
    const errDetails = JSON.stringify({ Errors: fleetRes.Errors }, null, 2);
    throw new Error(`EC2 Fleet did not return an instance ID. Details: ${errDetails}`);
  }

  core.info(`Successfully started AWS EC2 instance ${ec2InstanceId} with type ${ec2InstanceType} via EC2 Fleet in region ${region}`);

  // clean up the temporary launch template if it was created
  if (createdTemporaryLt && launchTemplateId) {
    try {
      await ec2.send(new DeleteLaunchTemplateCommand({ LaunchTemplateId: launchTemplateId }));
      core.info(`Deleted temporary Launch Template ${launchTemplateId}`);
    } catch (e) {
      core.warning(`Failed to delete temporary Launch Template ${launchTemplateId}: ${e.message}`);
    }
  }
  return ec2InstanceId;
}