app/models/BaseImage.scala (131 lines of code) (raw):
package models
import org.scanamo.DynamoFormat
import models.packer.PackerProvisionerConfig
import org.joda.time.DateTime
import cats.syntax.either._
import org.scanamo.TypeCoercionError
import BakeId.toMetadata
import BakeId.toFilename
import data.PackageList
sealed trait LinuxDist {
val name: String
val provisioners: Seq[PackerProvisionerConfig]
def savePackageListCommand(bakeId: BakeId): String
val loginName: String
}
object LinuxDist {
implicit val dynamoFormat: DynamoFormat[LinuxDist] =
DynamoFormat.xmap[LinuxDist, String](
value =>
Either.fromOption(
LinuxDist.create(value),
TypeCoercionError(
new RuntimeException(s"$value is not a known LinuxDist")
)
),
_.name
)
def create(name: String): Option[LinuxDist] = all.get(name)
def packageListTempPath(bakeId: BakeId) = s"/tmp/${toFilename(bakeId)}"
def uploadPackageListCommand(bakeId: BakeId, region: String, bucket: String) =
s"aws s3 cp ${packageListTempPath(bakeId)} ${PackageList.s3Url(bakeId, bucket)} --region ${region} --metadata ${toMetadata(bakeId)}"
val all = Map(
"ubuntu" -> Ubuntu,
"redhat" -> RedHat,
"amazon linux 2" -> AmazonLinux2
)
}
case object Ubuntu extends LinuxDist {
val name = "ubuntu"
val loginName = "ubuntu"
val provisioners = Seq(
// bootstrap Ansible
PackerProvisionerConfig.executeRemoteCommands(
Seq(
// Wait for cloud-init to finish first: https://github.com/mitchellh/packer/issues/2639
"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done",
"DEBIAN_FRONTEND=noninteractive apt-get --yes install software-properties-common",
"version=$(. /etc/os-release; echo $VERSION_ID | cut -d'.' -f1)",
"if (($version < 20)); then apt-add-repository --yes ppa:ansible/ansible; fi",
// ansible ppa broken in ubuntu: https://github.com/ansible/ansible/issues/69203
// and available in https://packages.ubuntu.com/focal/ansible
"apt-get --yes update",
"DEBIAN_FRONTEND=noninteractive apt-get --yes install ansible"
)
)
)
def savePackageListCommand(bakeId: BakeId) =
s"dpkg-query -W > ${LinuxDist.packageListTempPath(bakeId)}"
}
case object RedHat extends LinuxDist {
val name = "redhat"
val loginName = "ec2-user"
val provisioners = Seq(
PackerProvisionerConfig.executeRemoteCommands(
Seq(
"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done",
"rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm",
"yum -y update",
"yum -y install ansible",
"yum -y install libselinux-python-2.0.94-7.el6"
)
)
)
def savePackageListCommand(bakeId: BakeId) =
s"yum list installed > ${LinuxDist.packageListTempPath(bakeId)}"
}
case object AmazonLinux2 extends LinuxDist {
val name = "amazon linux 2"
val loginName = "ec2-user"
val provisioners = Seq(
PackerProvisionerConfig.executeRemoteCommands(
Seq(
"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done",
"yum -y update",
"yum -y install amazon-linux-extras", // should be a no-op
"amazon-linux-extras enable ansible2",
"yum -y install ansible"
)
)
)
def savePackageListCommand(bakeId: BakeId) =
s"yum list installed > ${LinuxDist.packageListTempPath(bakeId)}"
}
case class BaseImage(
id: BaseImageId,
description: String,
amiId: AmiId,
builtinRoles: List[CustomisedRole],
createdBy: String,
createdAt: DateTime,
modifiedBy: String,
modifiedAt: DateTime,
linuxDist: Option[LinuxDist] = None,
eolDate: Option[DateTime] = None,
requiresXLargeBuilder: Boolean = false
)
sealed trait EolStatus
case object EndOfLife extends EolStatus
case object EndOfLifeSoon extends EolStatus
case object Supported extends EolStatus
object BaseImage {
def eolStatus(image: BaseImage): EolStatus = {
if (image.eolDate.exists(_.isBefore(DateTime.now))) {
EndOfLife
} else if (image.eolDate.exists(_.isBefore((DateTime.now.plusMonths(3))))) {
EndOfLifeSoon
} else Supported
}
def eolStatusClass(image: BaseImage): String = eolStatus(image) match {
case EndOfLife => "end-of-life"
case EndOfLifeSoon => "end-of-life-soon"
case Supported => "supported"
}
def eolStatusString(image: BaseImage): String = {
val eolDateString =
image.eolDate.map(_.toString("dd/MM/yyyy")).getOrElse("unknown")
eolStatus(image) match {
case EndOfLife =>
s"WARNING: Base image no longer supported. Support ended on ${eolDateString}"
case EndOfLifeSoon =>
s"WARNING: Base image end of life on ${eolDateString}"
case Supported => s"Base image supported until ${eolDateString}"
}
}
}