app/controllers/Api.scala (434 lines of code) (raw):
package controllers
import agent._
import collectors._
import conf.PrismConfiguration
import data.Owners
import play.api.http.Status
import play.api.libs.json.Json._
import play.api.libs.json._
import play.api.mvc.{Action, RequestHeader, Result, _}
import utils.{Matchable, ResourceFilter}
import scala.concurrent.{ExecutionContext, Future}
import scala.language.postfixOps
//noinspection TypeAnnotation
class Api(
cc: ControllerComponents,
prismDataStore: Prism,
prismConfiguration: PrismConfiguration
)(implicit executionContext: ExecutionContext)
extends AbstractController(cc) {
implicit def referenceWrites[T <: IndexedItem](implicit
arnLookup: ArnLookup[T],
tWrites: Writes[T],
request: RequestHeader
): Writes[Reference[T]] = (o: Reference[T]) => {
request.getQueryString("_reference") match {
case Some("inline") =>
arnLookup
.item(o.arn, prismDataStore)
.flatMap { case (label, t) =>
Api.itemJson(item = t, label = Some(label), expand = true)
}
.getOrElse(JsString(o.arn))
case Some("uri") =>
Json.toJson(arnLookup.call(o.arn).absoluteURL()(request))
case _ =>
Json.toJson(o.arn)
}
}
import jsonimplicits.model._
def sortString(jsv: JsValue): String =
jsv match {
case JsString(str) => str
case JsArray(seq) => seq.map(sortString).mkString
case JsObject(fields) =>
fields.map { case (key, value) => s"$key${sortString(value)}" }.mkString
case _ => ""
}
def summary[T <: IndexedItem](
sourceAgent: CollectorAgent[T],
transform: T => Iterable[JsValue],
key: String,
enableFilter: Boolean = false
)(implicit ordering: Ordering[String]): Action[AnyContent] =
Action.async { implicit request =>
ApiResult.filter[JsValue] {
sourceAgent
.get()
.map { datum => datum.label -> datum.data.flatMap(transform) }
.toMap
} reduce { transformed =>
val objects =
transformed.values.toSeq.flatten.distinct.sortBy(sortString)(ordering)
val filteredObjects = if (enableFilter) {
val filter = ResourceFilter.fromRequest
objects.filter(filter.isMatch)
} else objects
Json.obj(key -> Json.toJson(filteredObjects))
}
}
def summaryFromTwo[T <: IndexedItem, U <: IndexedItem](
sourceTAgent: CollectorAgent[T],
transformT: T => Iterable[JsValue],
sourceUAgent: CollectorAgent[U],
transformU: U => Iterable[JsValue],
key: String,
enableFilter: Boolean = false
)(implicit ordering: Ordering[String]) =
Action.async { implicit request =>
ApiResult.filter[JsValue] {
sourceTAgent
.get()
.map { datum => datum.label -> datum.data.flatMap(transformT) }
.toMap ++
sourceUAgent
.get()
.map { datum => datum.label -> datum.data.flatMap(transformU) }
.toMap
} reduce { transformed =>
val objects =
transformed.values.toSeq.flatten.distinct.sortBy(sortString)(ordering)
val filteredObjects = if (enableFilter) {
val filter = ResourceFilter.fromRequest
objects.filter(filter.isMatch)
} else objects
Json.obj(key -> Json.toJson(filteredObjects))
}
}
def sources = Action.async { implicit request =>
ApiResult.filter {
val filter = ResourceFilter.fromRequest
val sources = prismDataStore.sourceStatusAgent.sources
Map(sources.label -> sources.data.map(toJson(_)).filter(filter.isMatch))
} reduce { collection =>
toJson(collection.flatMap(_._2))
}
}
def sourceAccounts: Action[AnyContent] = Action.async { implicit request =>
ApiResult.noSource {
val accounts = prismDataStore.accounts.all
.collect {
case result: AmazonOrigin => {
val accountName = result.account
val accountNumber = result.accountNumber
AWSAccount(accountNumber, accountName)
}
}
.toList
.distinct
Json.toJson(accounts)
}
}
def healthCheck = Action.async { implicit request =>
ApiResult.filter {
val sources = prismDataStore.sourceStatusAgent.sources
val notInitialisedSources =
sources.data.filter(_.state.status != "success")
if (notInitialisedSources.isEmpty) Map.empty
else Map(sources.label -> notInitialisedSources)
} reduce { notInitialisedSources =>
if (notInitialisedSources.isEmpty) {
Json.obj("healthcheck" -> "initialised")
} else
throw ApiCallException(
Json.obj(
"healthcheck" -> "not yet initialised",
"sources" -> notInitialisedSources.values.headOption
),
SERVICE_UNAVAILABLE
)
}
}
def find = Action.async { implicit request =>
val filter = ResourceFilter.fromRequest
ApiResult.filter {
val sources = prismDataStore.allAgents.map(_.get())
sources.flatMap { agent =>
agent.map { datum =>
datum.label -> datum.data.filter(d => filter.isMatch(d.fieldIndex))
}
}.toMap
} reduce { sources =>
val results = sources.flatMap { case (label, dataItems) =>
dataItems.map { data =>
Json.obj(
"type" -> label.resourceType.name,
"href" -> data.call.absoluteURL()
)
}
}
Json.toJson(results)
}
}
def instanceList = Action.async { implicit request =>
Api.itemList(
prismDataStore.instanceAgent,
"instances",
"vendorState" -> "running",
"vendorState" -> "ACTIVE"
)
}
def instance(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.instanceAgent, arn)
}
def lambdaList = Action.async { implicit request =>
Api.itemList(prismDataStore.lambdaAgent, "lambdas")
}
def lambda(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.lambdaAgent, arn)
}
def securityGroupList = Action.async { implicit request =>
Api.itemList(prismDataStore.securityGroupAgent, "security-groups")
}
def securityGroup(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.securityGroupAgent, arn)
}
def imageList = Action.async { implicit request =>
Api.itemList(prismDataStore.imageAgent, "images")
}
def image(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.imageAgent, arn)
}
def launchConfigurationList = Action.async { implicit request =>
Api.itemList(
prismDataStore.launchConfigurationAgent,
"launch-configurations"
)
}
def launchConfiguration(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.launchConfigurationAgent, arn)
}
def launchTemplateList = Action.async { implicit request =>
Api.itemList(
prismDataStore.launchTemplateAgent,
"active-launch-template-versions"
)
}
def launchTemplate(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.launchTemplateAgent, arn)
}
def acmCertificateList = Action.async { implicit request =>
Api.itemList(prismDataStore.acmCertificateAgent, "acm-certificates")
}
def acmCertificate(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.acmCertificateAgent, arn)
}
def route53ZoneList = Action.async { implicit request =>
Api.itemList(prismDataStore.route53ZoneAgent, "route53-zones")
}
def route53Zone(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.route53ZoneAgent, arn)
}
def elbList = Action.async { implicit request =>
Api.itemList(prismDataStore.elbAgent, "elbs")
}
def elb(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.elbAgent, arn)
}
def bucketList = Action.async { implicit request =>
Api.itemList(prismDataStore.bucketAgent, "buckets")
}
def bucket(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.bucketAgent, arn)
}
def reservationList = Action.async { implicit request =>
Api.itemList(prismDataStore.reservationAgent, "reservations")
}
def reservation(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.reservationAgent, arn)
}
def vpcList = Action.async { implicit request =>
Api.itemList(prismDataStore.vpcAgent, "vpcs")
}
def vpcs(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.vpcAgent, arn)
}
private def stackExtractor(i: IndexedItemWithStack) =
i.stack.map(Json.toJson(_))
private def stageExtractor(i: IndexedItemWithStage) =
i.stage.map(Json.toJson(_))
def roleList = summary[Instance](
prismDataStore.instanceAgent,
i => i.role.map(Json.toJson(_)),
"roles"
)
def mainclassList = summary[Instance](
prismDataStore.instanceAgent,
i => i.mainclasses.map(Json.toJson(_)),
"mainclasses"
)
def stackList = summaryFromTwo[Instance, Lambda](
prismDataStore.instanceAgent,
stackExtractor,
prismDataStore.lambdaAgent,
stackExtractor,
"stacks"
)(prismConfiguration.stages.ordering)
def stageList = summaryFromTwo[Instance, Lambda](
prismDataStore.instanceAgent,
stageExtractor,
prismDataStore.lambdaAgent,
stageExtractor,
"stages"
)(prismConfiguration.stages.ordering)
def regionList = summary[Instance](
prismDataStore.instanceAgent,
i => Some(Json.toJson(i.region)),
"regions"
)
def vendorList = summary[Instance](
prismDataStore.instanceAgent,
i => Some(Json.toJson(i.vendor)),
"vendors"
)
private def appListExtractor(i: IndexedItemWithCoreTags) = i.app.flatMap {
app =>
i.stack.map(stack => Json.toJson(Map("stack" -> stack, "app" -> app)))
}
def appList = summaryFromTwo[Instance, Lambda](
prismDataStore.instanceAgent,
appListExtractor,
prismDataStore.lambdaAgent,
appListExtractor,
"app",
enableFilter = true
)
private def appsWithCdkVersionExtractor(i: IndexedItemWithCoreTags) =
i.app.map { app =>
Json.toJson(
Map(
"app" -> app,
"stack" -> i.stack.getOrElse("unknown"),
"stage" -> i.stage.getOrElse("unknown"),
"guCdkVersion" -> i.guCdkVersion.getOrElse("n/a"),
"guCdkPatternName" -> i.guCdkPatternName.getOrElse("unknown"),
"awsRuntime" -> i.awsRuntime,
"stackOwner" -> Owners
.forStack(i.stack.getOrElse("unknown"), i.stage, Some(app))
.id
)
)
}
def appsWithCdkVersion = summaryFromTwo[Instance, Lambda](
prismDataStore.instanceAgent,
appsWithCdkVersionExtractor,
prismDataStore.lambdaAgent,
appsWithCdkVersionExtractor,
"apps-with-cdk-version",
enableFilter = true
)
def dataList = Action.async { implicit request =>
Api.itemList(prismDataStore.dataAgent, "data")
}
def data(arn: String) = Action.async { implicit request =>
Api.singleItem(prismDataStore.dataAgent, arn)
}
def dataKeysList = summary[Data](
prismDataStore.dataAgent,
d => Some(Json.toJson(d.key)),
"keys"
)
def dataLookup(key: String) = Action.async { implicit request =>
ApiResult.filter {
val app = request.getQueryString("app")
val stage = request.getQueryString("stage")
val stack = request.getQueryString("stack")
val validKey =
prismDataStore.dataAgent.getTuples.filter(_._2.key == key).toSeq
val errors: Map[String, String] = Map.empty ++
(if (app.isEmpty) Some("app" -> "Must specify app") else None) ++
(if (stage.isEmpty) Some("stage" -> "Must specify stage") else None) ++
(if (stage.isEmpty) Some("stack" -> "Must specify stack") else None) ++
(if (validKey.isEmpty) Some("key" -> s"The key name $key was not found")
else None) ++
(if (validKey.size > 1)
Some("key" -> s"The key name $key was matched multiple times")
else None)
if (errors.nonEmpty)
throw ApiCallException(Json.toJson(errors).as[JsObject])
val (label, data) = validKey.head
data
.firstMatchingData(stack.get, app.get, stage.get)
.map(data => Map(label -> Seq(data)))
.getOrElse {
throw ApiCallException(
Json.obj(
"value" -> s"Key $key has no matching value for stack=$stack, app=$app and stage=$stage"
)
)
}
} reduce { result =>
Json.toJson(result.head._2.head)
}
}
}
object Api extends Status {
import jsonimplicits.model._
def singleItem[T <: IndexedItem](agent: CollectorAgent[T], arn: String)(
implicit
request: RequestHeader,
writes: Writes[T],
executionContext: ExecutionContext
): Future[Result] =
ApiResult.filter {
val sources = agent.get()
sources.flatMap { datum =>
datum.data.find(_.arn == arn).map(datum.label -> Seq(_))
}.toMap
} reduce { sources =>
sources.headOption.map { case (label, items) =>
Api.itemJson(items.head, expand = true, label = Some(label)).get
} getOrElse {
throw ApiCallException(
Json.obj("arn" -> s"Item with arn $arn doesn't exist"),
NOT_FOUND
)
}
}
def itemJson[T <: IndexedItem](
item: T,
expand: Boolean = false,
label: Option[Label] = None,
filter: Matchable[JsValue] = ResourceFilter.all
)(implicit request: RequestHeader, writes: Writes[T]): Option[JsValue] = {
val json = Json.toJson(item).as[JsObject] ++ Json.obj(
"meta" -> Json.obj(
"href" -> item.call.absoluteURL(),
"origin" -> label.map(_.origin)
)
)
if (filter.isMatch(json)) {
val filtered =
if (expand) json
else JsObject(json.fields.filter(List("arn") contains _._1))
Some(filtered)
} else {
None
}
}
def itemList[T <: IndexedItem](
agent: CollectorAgentTrait[T],
objectKey: String,
defaultFilter: (String, String)*
)(implicit
request: RequestHeader,
writes: Writes[T],
executionContext: ExecutionContext
): Future[Result] =
ApiResult.filter {
val expand = request.getQueryString("_brief").isEmpty
val filter = ResourceFilter.fromRequestWithDefaults(defaultFilter: _*)
agent
.get()
.map { agent =>
agent.label ->
agent.data.flatMap(host =>
itemJson(host, expand, Some(agent.label), filter = filter)
)
}
.toMap
} reduce { collection =>
Json.obj(
objectKey -> toJson(collection.values.flatten)
)
}
}