app/conf/DynamoConfiguration.scala (89 lines of code) (raw):
package conf
import com.amazonaws.auth.AWSCredentialsProvider
import com.amazonaws.regions.Regions
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder
import com.amazonaws.services.dynamodbv2.document.{
DynamoDB,
Item,
PrimaryKey,
TableKeysAndAttributes
}
import com.typesafe.config.ConfigFactory
import play.api.{Configuration, Mode}
import utils.Logging
import scala.jdk.CollectionConverters._
case class Identity(stack: String, app: String, stage: String)
case class ConfigSegment(app: String, stage: String)
object DynamoConfiguration {
def apply(
credProvider: AWSCredentialsProvider,
region: Regions,
identity: Identity,
prefix: String = "config-"
): ConfigurationSource = {
new DynamoConfiguration(credProvider, region, identity, prefix)
}
}
class DynamoConfiguration(
credProvider: AWSCredentialsProvider,
region: Regions,
identity: Identity,
prefix: String
) extends ConfigurationSource
with Logging {
def configuration(mode: Mode): Configuration = {
if (mode == Mode.Test)
Configuration.empty
else {
val client = AmazonDynamoDBClientBuilder
.standard()
.withCredentials(credProvider)
.withRegion(region)
.build()
val dynamoDb = new DynamoDB(client)
val tableName = s"$prefix${identity.stack}"
val configs = fromTable(
dynamoDb,
tableName,
configSegmentsFromIdentity(identity)
)
val finalConfig = configs
.flatMap { case (_, config) => config }
.foldRight[Configuration](Configuration.empty) { _.withFallback(_) }
finalConfig
}
}
def fromTable(
dynamoDb: DynamoDB,
tableName: String,
configSegments: Seq[ConfigSegment]
): Seq[(ConfigSegment, Option[Configuration])] = {
val primaryKeys = configSegments.map { segment =>
new PrimaryKey("App", segment.app, "Stage", segment.stage)
}
val tableKeysAndAttributes =
new TableKeysAndAttributes(tableName).withPrimaryKeys(primaryKeys: _*)
val result = dynamoDb.batchGetItem(tableKeysAndAttributes)
val items =
result.getTableItems.asScala.get(tableName).toSeq.flatMap(_.asScala)
val configs = items.map { item =>
val app = item.getString("App")
val stage = item.getString("Stage")
ConfigSegment(app, stage) -> fromItem(
item,
s"Dynamo DB table $tableName [App=$app, Stage=$stage]"
)
}.toMap
configSegments.map { segment => segment -> configs.get(segment) }
}
def fromItem(item: Item, originDescription: String): Configuration = {
Configuration(
ConfigFactory.parseMap(item.getMap("Config"), originDescription)
)
}
def configSegmentsFromIdentity(identity: Identity) = Seq(
ConfigSegment("global", "global"),
ConfigSegment(identity.app, "global"),
ConfigSegment(identity.app, identity.stage)
)
}