app/services/notification/ScheduledNotificationRunner.scala (140 lines of code) (raw):
package services.notification
import com.amazonaws.services.simpleemail.model._
import config.{AMIableConfig, AmiableConfigProvider}
import javax.inject.Inject
import models._
import org.joda.time.DateTime
import play.api.{Environment, Logging, Mode}
import prism.{Prism, PrismLogic}
import utils.DateUtils
import scala.concurrent.ExecutionContext
class ScheduledNotificationRunner @Inject() (
mailClient: AWSMailClient,
environment: Environment,
amiableConfigProvider: AmiableConfigProvider
)(implicit ec: ExecutionContext) {
implicit val config: AMIableConfig = amiableConfigProvider.conf
def run(today: DateTime): Attempt[List[String]] = {
for {
instancesWithAmis <- Prism.instancesWithAmis(SSAA(stage = Some("PROD")))
giantInstances <- Prism.instancesWithAmis(
SSAA(stage = Some("rex"), stack = Some("pfi-giant"))
)
allProdInstancesWithAmis = instancesWithAmis ++ giantInstances
oldInstances = PrismLogic.oldInstances(allProdInstancesWithAmis)
instanceAmiMap = ScheduledNotificationRunner.makeInstanceAmiMap(
allProdInstancesWithAmis
)
ownersWithDefault <- Prism.getOwners
ownersAndOldInstances = ScheduledNotificationRunner.findInstanceOwners(
oldInstances,
ownersWithDefault
)
mailIds <- Attempt.traverse(ownersAndOldInstances.toList) {
case (owner, oldInstancesForOwner) =>
val instances = ScheduledNotificationRunner.pairInstancesWithAmi(
oldInstancesForOwner,
instanceAmiMap
)
val request = ScheduledNotificationRunner.createEmailRequest(
owner,
instances,
config,
today
)
ScheduledNotificationRunner.conditionallySendEmail(
environment.mode,
config.overrideToAddress,
mailClient,
owner,
request
)
}
} yield mailIds
}
}
object ScheduledNotificationRunner extends Logging {
def ownerForInstance(i: Instance, owners: Owners): Owner = {
owners.owners
.find(_.hasSSA(SSAA(i.stack, i.stage, i.app.headOption)))
.orElse(
owners.owners.find(_.hasSSA(SSAA(i.stack, app = i.app.headOption)))
)
.orElse(owners.owners.find(_.hasSSA(SSAA(i.stack, i.stage))))
.orElse(owners.owners.find(_.hasSSA(SSAA(i.stack))))
.getOrElse(owners.defaultOwner)
}
def makeInstanceAmiMap(
instancesWithAmis: List[(Instance, Option[AMI])]
): Map[Instance, AMI] =
instancesWithAmis.collect { case (i, Some(ami)) => i -> ami }.toMap
def pairInstancesWithAmi(
instances: List[Instance],
instanceAmiMap: Map[Instance, AMI]
): List[(Instance, Option[AMI])] = {
instances
.map(i => i -> instanceAmiMap.get(i))
.sortBy { case (i, maybeAmi) =>
val maybeCreationTimestamp =
maybeAmi.flatMap(_.creationDate).map(_.getMillis)
(maybeCreationTimestamp, i.stack, i.stage, i.app.headOption)
}
}
def findInstanceOwners(
instances: List[Instance],
owners: Owners
): Map[Owner, List[Instance]] = {
instances
.map { i => (i, ScheduledNotificationRunner.ownerForInstance(i, owners)) }
.groupBy(_._2)
.view
.mapValues(_.map(_._1))
.toMap
}
def createEmailRequest(
owner: Owner,
instances: Seq[(Instance, Option[AMI])],
config: AMIableConfig,
today: DateTime
): SendEmailRequest = {
val toAddress =
config.overrideToAddress.getOrElse(s"${owner.id}@guardian.co.uk")
val todaysDate = DateUtils.yearMonthDay.print(today)
val destination = new Destination().withToAddresses(toAddress)
val emailSubject = new Content().withData(
s"Instances using out of date AMIs (as of $todaysDate, owned by ${owner.id})"
)
val htmlBody = new Content().withData(
views.html.email(config.amiableUrl, instances, owner).toString()
)
val body = new Body().withHtml(htmlBody)
val emailMessage = new Message().withSubject(emailSubject).withBody(body)
val request = new SendEmailRequest()
.withSource(config.mailAddress)
.withDestination(destination)
.withMessage(emailMessage)
request
}
def conditionallySendEmail(
mode: Mode,
overrideToAddress: Option[String],
mailClient: AWSMailClient,
owner: Owner,
request: SendEmailRequest
): Attempt[String] = {
(mode, overrideToAddress) match {
case (Mode.Prod, maybeOverride) =>
mailClient.send(
maybeOverride.getOrElse(s"${owner.id}@guardian.co.uk"),
request
)
case (_, Some(overrideToaddress)) =>
mailClient.send(overrideToaddress, request)
case (_, None) =>
logger.info(
s"Not in Prod and no override To Address set. Would have sent email to ${owner.id}, request: $request"
)
Attempt.Right("")
}
}
}