app/packer/PackerBuildConfigGenerator.scala (80 lines of code) (raw):
package packer
import models.packer._
import models.{Bake, LinuxDist, Ubuntu}
import services.AmiMetadata
import java.nio.file.{Path, Paths}
object PackerBuildConfigGenerator {
val stage: String = "INFRA"
val stack: String = "amigo-packer"
/** Generates a Packer build config that:
* - starts an EC2 machine
* - installs Ansible
* - runs Ansible to install the required roles
* - tags the resulting AMI with the recipe ID and build number
*/
def generatePackerBuildConfig(
amigoStage: String,
bake: Bake,
playbookFile: Path,
variables: PackerVariablesConfig,
awsAccountNumbers: Seq[String],
sourceAmiMetadata: AmiMetadata,
amigoDataBucket: Option[String],
requiresXlargeBuilder: Boolean
)(implicit packerConfig: PackerConfig): PackerBuildConfig = {
val awsAccounts = awsAccountNumbers.mkString(",")
val imageDetails = ImageDetails.apply(variables, packerConfig.stage)
val region = "eu-west-1"
val disk = bake.recipe.diskSize.map(size =>
List(BlockDeviceMapping(volume_size = size))
)
val instanceSize = if (requiresXlargeBuilder) "xlarge" else "small"
val instanceType = sourceAmiMetadata.architecture match {
case "x86_64" => s"t3.$instanceSize"
case "arm64" => s"t4g.$instanceSize"
case other =>
throw new IllegalArgumentException(
s"Don't know what instance type to use to bake an AMI for $other"
)
}
// here we are using requiresXlargeBuilder as an indicator that freezing the packer instance to an AMI will take longer
// 15, 240 = poll every 15 seconds, stop after 240 attempts (total 1 hour)
val awsPolling =
if (requiresXlargeBuilder) Some(AwsPolling(15, 240)) else None
val builder = PackerBuilderConfig(
name = "{{user `recipe`}}",
`type` = "amazon-ebs",
region = region,
vpc_id = packerConfig.vpcId,
subnet_id = packerConfig.subnetId,
source_ami = "{{user `base_image_ami_id`}}",
instance_type = instanceType,
ssh_username =
bake.recipe.baseImage.linuxDist.getOrElse(Ubuntu).loginName,
ssh_interface = "session_manager",
run_tags = Map(
"Name" -> "Packer Builder",
"Stage" -> stage,
"AmigoStage" -> amigoStage,
"Stack" -> stack,
"App" -> "{{user `recipe`}}",
"BakeId" -> s"${bake.bakeId.toString}"
),
ami_name = imageDetails.name,
ami_description = imageDetails.description,
ami_users = awsAccounts,
snapshot_users = awsAccounts,
iam_instance_profile = packerConfig.instanceProfile,
tags = imageDetails.tags,
ami_block_device_mappings = disk,
launch_block_device_mappings = disk,
security_group_id = packerConfig.securityGroupId,
aws_polling = awsPolling
)
val baseImage = bake.recipe.baseImage.linuxDist.getOrElse(Ubuntu)
val uploadPackagesCommand = amigoDataBucket.map { bucket =>
PackerProvisionerConfig.executeRemoteCommands(
Seq(
baseImage.savePackageListCommand(bake.bakeId),
LinuxDist.uploadPackageListCommand(bake.bakeId, region, bucket)
)
)
}.toSeq
val provisioners = baseImage.provisioners ++ Seq(
PackerProvisionerConfig.ansibleLocal(playbookFile, Paths.get("roles"))
) ++ uploadPackagesCommand
PackerBuildConfig(variables, Seq(builder), provisioners)
}
}