app/conf/Configuration.scala (164 lines of code) (raw):

package conf import java.io.{File, FileInputStream, InputStream} import java.net.URL import com.amazonaws.AmazonClientException import com.amazonaws.auth.profile.ProfileCredentialsProvider import com.amazonaws.auth.{AWSCredentialsProvider, AWSCredentialsProviderChain, InstanceProfileCredentialsProvider} import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration import com.amazonaws.regions.{Region, RegionUtils} import com.amazonaws.services.cloudwatch.AmazonCloudWatch import com.amazonaws.services.dynamodbv2.AmazonDynamoDB import com.amazonaws.services.s3.{AmazonS3, AmazonS3ClientBuilder} import com.gu.permissions.PermissionsConfig import org.apache.commons.io.IOUtils import play.api.Mode import play.api.{Configuration => PlayConfiguration} import story_packages.services.Logging import scala.language.reflectiveCalls import scala.jdk.CollectionConverters._ class BadConfigurationException(msg: String) extends RuntimeException(msg) class ApplicationConfiguration(val playConfiguration: PlayConfiguration, val envMode: Mode) extends Logging { private val propertiesFile = "/etc/gu/story-packages.properties" private val installVars = new File(propertiesFile) match { case f if f.exists => IOUtils.toString(new FileInputStream(f)) case _ => Logger.warn("Missing configuration file $propertiesFile") "" } private val properties = Properties(installVars) private val stageFromProperties = properties.getOrElse("STAGE", "CODE") private def getString(property: String): Option[String] = playConfiguration.getOptional[String](stageFromProperties + "." + property) .orElse(playConfiguration.getOptional[String](property)) private def getMandatoryString(property: String): String = getString(property) .getOrElse(throw new BadConfigurationException(s"$property of type string not configured for stage $stageFromProperties")) private def getBoolean(property: String): Option[Boolean] = playConfiguration.getOptional[Boolean](stageFromProperties + "." + property) .orElse(playConfiguration.getOptional[Boolean](property)) private def getMandatoryBoolean(property: String): Boolean = getBoolean(property) .getOrElse(throw new BadConfigurationException(s"$property of type boolean not configured for stage $stageFromProperties")) object environment { lazy val applicationName: String = getMandatoryString("environment.applicationName") val stage: String = stageFromProperties.toLowerCase val mode = envMode } object aws { lazy val region = getMandatoryString("aws.region") lazy val bucket = getMandatoryString("aws.bucket") object endpoints { private val _region = RegionUtils.getRegion(region) val monitoring: String = _region.getServiceEndpoint(AmazonCloudWatch.ENDPOINT_PREFIX) val dynamoDB: String = _region.getServiceEndpoint(AmazonDynamoDB.ENDPOINT_PREFIX) val s3: String = _region.getServiceEndpoint(AmazonS3.ENDPOINT_PREFIX) } def mandatoryCredentials: AWSCredentialsProvider = credentials.getOrElse(throw new BadConfigurationException("AWS credentials are not configured")) val credentials: Option[AWSCredentialsProvider] = { val provider = new AWSCredentialsProviderChain( new ProfileCredentialsProvider("cmsFronts"), InstanceProfileCredentialsProvider.getInstance ) // this is a bit of a convoluted way to check whether we actually have credentials. // I guess in an ideal world there would be some sort of isConfigued() method... try { val creds = provider.getCredentials Some(provider) } catch { case ex: AmazonClientException => Logger.error("amazon client exception") // We really, really want to ensure that PROD is configured before saying a box is OK if (envMode == Mode.Prod) throw ex // this means that on dev machines you only need to configure keys if you are actually going to use them None } } val s3Client: Option[AmazonS3] = credentials.map { credentials => AmazonS3ClientBuilder.standard .withCredentials(credentials) .withEndpointConfiguration(new EndpointConfiguration(endpoints.s3, region)) .build } } object contentApi { val contentApiLiveHost: String = getMandatoryString("content.api.host") val packagesLiveHost: String = getString("content.api.packages.host").getOrElse(contentApiLiveHost) val contentApiDraftHost: String = getMandatoryString("content.api.draft.iam-host") val packagesDraftHost: String = getString("content.api.packages.draft.host").getOrElse(contentApiDraftHost) lazy val key: Option[String] = getString("content.api.key") lazy val previewRole = getMandatoryString("content.api.draft.role") } object facia { lazy val stage = getString("facia.stage").getOrElse(stageFromProperties) val includedCollectionCap: Int = 12 val linkingCollectionCap: Int = 50 } object logging { lazy val stream = getMandatoryString("logging.kinesis.stream") lazy val streamRegion = getMandatoryString("logging.kinesis.region") lazy val streamRole = getMandatoryString("logging.kinesis.roleArn") lazy val app = getMandatoryString("logging.fields.app") lazy val enabled = getBoolean("logging.enabled").getOrElse(false) } object media { lazy val baseUrl = getString("media.base.url") lazy val apiUrl = getString("media.api.url") } object ophanApi { lazy val key = getString("ophan.api.key") lazy val host = getString("ophan.api.host") } object pandomain { lazy val host = getMandatoryString("pandomain.host") lazy val domain = getMandatoryString("pandomain.domain") lazy val bucketName = getMandatoryString("pandomain.bucketName") lazy val settingsFileKey = s"$domain.settings" lazy val service = getMandatoryString("pandomain.service") lazy val roleArn = getMandatoryString("pandomain.roleArn") } object sentry { lazy val publicDSN = getString("sentry.publicDSN").getOrElse("") } object storage { val configTable = properties.getOrElse("TABLE_CONFIG", throw new BadConfigurationException("Missing TABLE_CONFIG property")) val maxPageSize = 50 val maxLatestDays = 15 val maxLatestResults = 50 } object switchBoard { val bucket = getMandatoryString("switchboard.bucket") val objectKey = getMandatoryString("switchboard.object") } object updates { lazy val capi: String = properties.getOrElse("CAPI_STREAM", throw new BadConfigurationException("CAPI stream name is not configured")) lazy val preview: String = properties.getOrElse("PREVIEW_CAPI_STREAM", throw new BadConfigurationException("CAPI stream name is not configured")) lazy val reindex: String = properties.getOrElse("REINDEX_STREAM", throw new BadConfigurationException("REINDEX stream name is not configured")) lazy val reindexPreview: String = properties.getOrElse("PREVIEW_REINDEX_STREAM", throw new BadConfigurationException("REINDEX stream name is not configured")) lazy val maxDataSize: Int = 1024000 } object reindex { lazy val key: String = getMandatoryString("reindex.key") lazy val progressTable: String = properties.getOrElse("REINDEX_TABLE", throw new BadConfigurationException("REINDEX_TABLE is not configured")) } object latest { lazy val pageSize = 20 } val permissions = PermissionsConfig( stage = environment.stage.toUpperCase, region = aws.region, awsCredentials = aws.mandatoryCredentials, ) } object Properties extends AutomaticResourceManagement { def apply(is: InputStream): Map[String, String] = { val properties = new java.util.Properties() withCloseable(is) { properties load _ } properties.asScala.toMap } def apply(text: String): Map[String, String] = apply(IOUtils.toInputStream(text)) def apply(file: File): Map[String, String] = apply(new FileInputStream(file)) def apply(url: URL): Map[String, String] = apply(url.openStream) } trait AutomaticResourceManagement { def withCloseable[T <: { def close(): Unit }](closeable: T) = new { def apply[S](body: T => S) = try { body(closeable) } finally { closeable.close() } } }