app/prism/Prism.scala (116 lines of code) (raw):
package prism
import models.AmiId
import play.api.data.validation.ValidationError
import play.api.libs.json._
import play.api.libs.ws.WSClient
import services.Loggable
import scala.concurrent.{ExecutionContext, Future}
class Prism(
ws: WSClient,
val baseUrl: String = "https://prism.gutools.co.uk"
)(implicit
ec: ExecutionContext
) extends Loggable {
import Prism._
def findAllAWSAccounts(): Future[Seq[AWSAccount]] = {
findAll[AWSAccount]("/sources?resource=instance&origin.vendor=aws")
}
def findAllInstances(): Future[Seq[Instance]] =
findAll[Instance]("/instances")
def findAllLaunchConfigurations(): Future[Seq[LaunchConfiguration]] =
findAll[LaunchConfiguration]("/launch-configurations")
def findAllLaunchTemplates(): Future[Seq[LaunchTemplate]] =
findAll[LaunchTemplate]("/active-launch-template-versions")
def findCopiedImages(): Future[Seq[Image]] =
findAll[Image]("/images?tags.CopiedFromAMI!=")
private def findAll[T](
path: String
)(implicit r: Reads[Seq[T]]): Future[Seq[T]] = {
val url = s"$baseUrl$path"
ws.url(url).get().flatMap { resp =>
val prismResponse = for {
stale <- (resp.json \ "stale").validate[JsBoolean].map(_.value)
data <- r.reads(resp.json)
} yield (stale, data)
prismResponse.fold(
error => {
val message =
s"Failed to parse Prism response for GET $url. Status code = ${resp.status}, Error = $error"
log.warn(message)
Future.failed(new IllegalStateException(message))
},
{
case (true, _) =>
Future.failed(
new IllegalStateException(
s"Prism response indicated it was stale on $url"
)
)
case (false, t) => Future.successful(t)
}
)
}
}
}
object Prism {
case class AWSAccount(accountName: String, accountNumber: String)
case class Instance(name: String, imageId: AmiId, awsAccount: AWSAccount)
case class LaunchConfiguration(
name: String,
imageId: AmiId,
awsAccount: AWSAccount
)
case class LaunchTemplate(
name: String,
imageId: AmiId,
awsAccount: AWSAccount
)
case class Image(
imageId: AmiId,
ownerId: String,
copiedFromAMI: AmiId,
encrypted: Option[String],
state: String
)
import play.api.libs.functional.syntax._
implicit val sourceInstanceReads: Reads[AWSAccount] =
((JsPath \ "origin" \ "accountName").read[String] and
(JsPath \ "origin" \ "accountNumber").read[String])(AWSAccount.apply _)
implicit val sourceInstancesReads: Reads[Seq[AWSAccount]] =
dataReads[AWSAccount](dataPath = "data")
implicit val instanceReads: Reads[Instance] =
((JsPath \ "instanceName").read[String] and
(JsPath \ "specification" \ "imageId").read[String].map(AmiId.apply) and
(JsPath \ "meta").read[AWSAccount])(Instance.apply _)
implicit val instancesReads: Reads[Seq[Instance]] =
dataReads[Instance](dataPath = "data", "instances")
implicit val launchConfigurationReads: Reads[LaunchConfiguration] =
((JsPath \ "name").read[String] and
(JsPath \ "imageId").read[String].map(AmiId.apply) and
(JsPath \ "meta").read[AWSAccount])(LaunchConfiguration.apply _)
implicit val launchTemplateReads: Reads[LaunchTemplate] =
((JsPath \ "name").read[String] and
(JsPath \ "imageId").read[String].map(AmiId.apply) and
(JsPath \ "meta").read[AWSAccount])(LaunchTemplate.apply _)
implicit val launchTemplatesReads: Reads[Seq[LaunchTemplate]] =
dataReads[LaunchTemplate](
dataPath = "data",
"active-launch-template-versions"
)
implicit val launchConfigurationsReads: Reads[Seq[LaunchConfiguration]] =
dataReads[LaunchConfiguration](dataPath = "data", "launch-configurations")
implicit val imageReads: Reads[Image] =
((JsPath \ "imageId").read[String].map(AmiId.apply) and
(JsPath \ "ownerId").read[String] and
(JsPath \ "tags" \ "CopiedFromAMI").read[String].map(AmiId.apply) and
(JsPath \ "tags" \ "Encrypted").readNullable[String] and
(JsPath \ "state").read[String])(Image.apply _)
implicit val imagesReads: Reads[Seq[Image]] =
dataReads[Image](dataPath = "data", "images")
private def dataReads[T](
dataPath: String*
)(implicit r: Reads[T]): Reads[Seq[T]] =
dataPath
.foldLeft[JsPath](__)((path, subPath) => path \ subPath)
.read[Seq[T]]
}