Skip to content

Commit

Permalink
Set up stage-specific config (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvin-chappell authored Feb 6, 2024
1 parent 732df86 commit 41bd21a
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 31 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
# Gatehouse

## Configuration

The app configuration has three levels of precedence. Any values repeated at a higher level will override those at a lower
level.
The levels are, in order of precedence:

### 1. SSM parameters
Secret and private settings are stored as
[AWS SSM parameters](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html),
named in the format:
`/<stage>/identity/gatehouse/<param key>`
where `param key` is a slash-separated
[Hocon](https://github.com/lightbend/config/blob/main/HOCON.md) key.

Eg.
`/CODE/identity/gatehouse/play/http/secret/key`
would give us a `play.http.secret.key` value.

Secrets are stored as `SecureString` parameters.
Private settings are stored as `String` parameters.

### 2. Stage-specific settings
Settings that aren't private but vary between deployment stages are set in the stage-specific config files in
the `conf` directory. These might be values that we expose in browsers, for example.

### 3. Global settings
Finally, settings that aren't private and are the same for all deployment stages are set in the `conf/application.conf`
file.
20 changes: 0 additions & 20 deletions app/load/AppComponents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,6 @@ import scala.util.Using

class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents {

private val region = Region.EU_WEST_1

private val stage = context.initialConfiguration.getOptional[String]("stage").getOrElse("DEV")

private lazy val secretKey: String = {
val request = GetParameterRequest.builder
.name(s"/$stage/identity/gatehouse/playSecret")
.withDecryption(true)
.build()
Using.resource(SsmClient.builder.region(region).build()) {
_.getParameter(request).parameter.value
}
}

override def configuration: Configuration =
if (stage == "DEV")
super.configuration
else
Configuration("play.http.secret.key" -> secretKey).withFallback(super.configuration)

override def httpFilters: Seq[EssentialFilter] = super.httpFilters :+ new RequestLoggingFilter(materializer)

lazy val healthCheckController = new controllers.HealthCheckController(controllerComponents)
Expand Down
44 changes: 41 additions & 3 deletions app/load/AppLoader.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,52 @@
package load

import com.gu.conf.{
ComposedConfigurationLocation,
ConfigurationLoader,
ResourceConfigurationLocation,
SSMConfigurationLocation
}
import com.gu.{AppIdentity, AwsIdentity}
import com.typesafe.config.Config
import play.api.*
import play.api.ApplicationLoader.Context
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider

import scala.util.{Failure, Success, Try}

class AppLoader extends ApplicationLoader {

private val appName = "gatehouse"

private def buildConfig(context: Context): Try[Config] = {
val credentialsProvider = DefaultCredentialsProvider.create()
val isDev = context.environment.mode == Mode.Dev
for {
identity <-
if (isDev)
Success(AwsIdentity(app = appName, stack = "identity", stage = "DEV", region = "eu-west-1"))
else
AppIdentity.whoAmI(defaultAppName = appName, credentialsProvider)
config <- Try(ConfigurationLoader.load(identity, credentialsProvider) { case identity: AwsIdentity =>
ComposedConfigurationLocation(
List(
SSMConfigurationLocation.default(identity),
ResourceConfigurationLocation(s"${identity.stage}.conf"),
)
)
})
} yield config
}

override def load(context: Context): Application = {
LoggerConfigurator(context.environment.classLoader).foreach {
_.configure(context.environment, context.initialConfiguration, Map.empty)
LoggerConfigurator(context.environment.classLoader) foreach { _.configure(context.environment) }
buildConfig(context) match {
case Success(config) =>
val newContext =
context.copy(initialConfiguration = Configuration(config).withFallback(context.initialConfiguration))
new AppComponents(newContext).application
case Failure(exception) =>
throw exception
}
new AppComponents(context).application
}
}
1 change: 0 additions & 1 deletion app/logging/LogEntry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ private[logging] object LogEntry {
def requestAndResponse(request: RequestHeader, response: Result, duration: Long): LogEntry = {
val fields = commonFields(request, duration) ++ Map(
"status" -> response.header.status,
"content_length" -> response.header.headers.getOrElse(CONTENT_LENGTH, 0),
"content_length" -> response.header.headers.get(CONTENT_LENGTH).map(_.toInt).getOrElse(0),
)
val message =
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ lazy val root = (project in file("."))
s"-J-Dlogs.home=/var/log/${packageName.value}",
),
libraryDependencies ++= Seq(
"software.amazon.awssdk" % "ssm" % "2.23.10",
"net.logstash.logback" % "logstash-logback-encoder" % "7.3",
("com.gu" %% "simple-configuration-ssm" % "1.6.4").cross(CrossVersion.for3Use2_13),
"org.scalatestplus.play" %% "scalatestplus-play" % "7.0.1" % Test,
),
)
2 changes: 1 addition & 1 deletion cdk/lib/__snapshots__/gatehouse.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ aws s3 cp 's3://",
"Ref": "DistributionBucketName",
},
"/identity/TEST/gatehouse/gatehouse.deb' '/gatehouse/gatehouse.deb'
dpkg -i /gatehouse/gatehouse.deb && echo "stage=TEST" | sudo tee "/etc/gatehouse/stage.conf" > /dev/null",
dpkg -i /gatehouse/gatehouse.deb",
],
],
},
Expand Down
2 changes: 1 addition & 1 deletion cdk/lib/gatehouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Gatehouse extends GuStack {
userData: {
distributable: {
fileName: `${ec2App}.deb`,
executionStatement: `dpkg -i /${ec2App}/${ec2App}.deb && echo "stage=${this.stage}" | sudo tee "/etc/gatehouse/stage.conf" > /dev/null`,
executionStatement: `dpkg -i /${ec2App}/${ec2App}.deb`,
},
},
certificateProps: {
Expand Down
1 change: 1 addition & 0 deletions conf/CODE.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include "application.conf"
1 change: 1 addition & 0 deletions conf/DEV.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include "application.conf"
1 change: 1 addition & 0 deletions conf/PROD.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include "application.conf"
5 changes: 1 addition & 4 deletions conf/application.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# https://www.playframework.com/documentation/latest/Configuration
include file("/etc/gatehouse/stage.conf")

play.application.loader=load.AppLoader

play.application.loader = load.AppLoader
play.filters.hosts.routeModifiers.whiteList = [anyhost]

0 comments on commit 41bd21a

Please sign in to comment.