Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
823b38f
[upstream] Fixes FileMetadataReaderTest fails locally during British …
tonytw1 Sep 20, 2025
2941b63
[upstream] Fixes Intellij sbt import after removing Dynamo SDK v1.
tonytw1 Apr 11, 2026
63831b3
[upstream] Fix off by 1 in message processing attempts log output.
tonytw1 Apr 23, 2026
a481d33
[upstream] testcontainers-elasticsearch for an easier local setup wit…
tonytw1 Mar 29, 2026
fe64c46
[upstream] CropController uses GridClient for it's get source image c…
tonytw1 May 8, 2024
3c03077
[upstream] Remove scala-xml version override.
tonytw1 Jan 18, 2026
1b6a8ee
[upstream] Owned illustrations should render with a blue border like …
tonytw1 Apr 28, 2026
5ebc89d
[upstream] Metadata getUsageRights end point accounts for the Edits.U…
tonytw1 Feb 28, 2026
008b891
[upstream] ThrallEventConsumerTest does not need to extend ElasticSea…
tonytw1 Aug 28, 2026
d66ed8f
For simpler test setup CollectionsStore takes specific Dynamo depende…
tonytw1 Mar 29, 2026
0bc87b0
CollectionsStore Test uses testcontainers localstack supplied dynamoDB.
tonytw1 Mar 29, 2026
29d1559
Tests for ImagesCollectionsStore.
tonytw1 Mar 8, 2026
cbed798
For simpler test setup DynamoDB base class takes specific Dynamo depe…
tonytw1 Mar 29, 2026
b747381
EditStores tests
tonytw1 Aug 27, 2026
3f21cc2
For simpler test setup LeaseStore takes specific Dynamo dependencies …
tonytw1 Mar 16, 2026
d6b88a2
LeaseStore tests
tonytw1 Mar 16, 2026
4d440b3
UsageTable tests for matchUsageGroup, update, markAsRemoved, and dele…
tonytw1 Mar 15, 2026
2ea2b0f
[containerised] Latest sbt 1.11.7
tonytw1 Jan 1, 2026
7e7e460
[containered] Clean up; CollectionManager private and types.
tonytw1 May 20, 2025
54ece51
Neuter CloudWatchMetrics; TODO push to config.
tonytw1 May 5, 2024
9ddaa26
Want to disable Kinises client's noisey CloudWatch emissions.
tonytw1 May 5, 2024
73bf5f9
Play secret from ENV; need to explicitly resolve placeholders.
tonytw1 May 29, 2024
b6d98a8
Initial docker image builds
tonytw1 May 4, 2024
4a3678f
Fork image loader specific play project.
tonytw1 May 7, 2024
3d53768
Cropper asks for 'gm' so give it the same Debian packages as image-up…
tonytw1 May 9, 2024
0757d59
build.sbt drop Debian package related config which is not needed for …
tonytw1 May 3, 2025
3df4bcc
Deprecation: `in` is deprecated; migrate to slash syntax
tonytw1 Dec 31, 2025
6f49715
Only log to stdout in the containerised world.
tonytw1 May 5, 2024
d42c29a
Alter Syndication access check to use URIs not raw host name; allows …
tonytw1 May 8, 2024
accab92
Introduce an interface to document the exposed service uris.
tonytw1 May 8, 2024
6efa54e
Everyone using GuardianUrlSchemeServices directly should take Service…
tonytw1 May 8, 2024
f0073a7
Drop GuardianUrlSchemeServices val constructor fields; these allow un…
tonytw1 May 8, 2024
0daf5af
Add a Service URL implementation which map services to port numbers o…
tonytw1 May 8, 2024
ce45f57
Drop Guardian services URL scheme.
tonytw1 Jun 3, 2024
43eba60
Move all public facing service urls to sub paths under single hostname.
tonytw1 May 10, 2024
bff26a8
Disable CSRF with is no longer bypassed on a single origin CORS check.
tonytw1 May 14, 2024
dc0a345
[containerised] Download crop asset links click through correctly whe…
tonytw1 Feb 11, 2026
02900f9
Delete InnerServiceStatusCheckController
tonytw1 Mar 29, 2026
f860b8d
Simplify reaper paused control to set by config only.
tonytw1 Jun 25, 2024
0ab4af7
[containered] End point to set a digital media usage.
tonytw1 Feb 21, 2026
2f673a8
Drop Guardian specific stream ingestion of usages.
tonytw1 Mar 29, 2026
2d62cbd
[containerised] Digital usage ids are prefixed with digital for consi…
tonytw1 Apr 5, 2026
f40f1f3
[digital-usage] Digital usage webTitle and sectionId are optional.
tonytw1 Feb 21, 2026
055cb12
Unused MediaWrapper class and helpers.
tonytw1 Mar 5, 2026
31b28bc
Drop Guardian Content references.
tonytw1 Mar 5, 2026
b7d8052
Unused ResetException.
tonytw1 Mar 5, 2026
5a058d7
Use imgproxy rather than nginx imgops for optimised image previews.
tonytw1 Jun 26, 2024
53b0165
Play base project heap size set to 40%; want more heap for given cont…
tonytw1 May 28, 2024
3c9b17f
image loader prints java memory settings.
tonytw1 May 14, 2025
2cb57af
Cloudbuild all artifacts as 1 build.
tonytw1 Sep 8, 2024
8f1485b
Cloudbuild runs Kahuna tests.
tonytw1 May 20, 2025
3ed614f
UsageTable methods which can be made private.
tonytw1 Mar 21, 2026
0229ff3
DynamoDB private methods.
tonytw1 Mar 21, 2026
b71b075
MetadataSqsMessageConsumer.processDeletedImage is private.
tonytw1 Mar 21, 2026
6713c94
Build vips-8.18.4 / JDK25 base image.
tonytw1 Apr 5, 2026
5c236e3
Use vips-8.18.4 base image for playImageLoaderProject.
tonytw1 Apr 5, 2026
f8e6e30
[containerised] We only need Environment credentials; may speed up fi…
tonytw1 Nov 21, 2024
8c9f198
Nerf EC2MetadataUtils.getInstanceId which has no meaning in a contain…
tonytw1 Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions auth/app/auth/AuthComponents.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package auth

import com.gu.mediaservice.lib.management.{InnerServiceStatusCheckController, Management}
import com.gu.mediaservice.lib.management.Management
import com.gu.mediaservice.lib.play.GridComponents
import play.api.ApplicationLoader.Context
import play.api.{Configuration, Environment}
Expand All @@ -14,10 +14,9 @@ class AuthComponents(context: Context) extends GridComponents(context, new AuthC

val controller = new AuthController(auth, providers, config, controllerComponents, authorisation)
val permissionsAwareManagement = new Management(controllerComponents, buildInfo)
val InnerServiceStatusCheckController = new InnerServiceStatusCheckController(auth, controllerComponents, config.services, wsClient)


override val router = new Routes(httpErrorHandler, controller, permissionsAwareManagement, InnerServiceStatusCheckController)
override val router = new Routes(httpErrorHandler, controller, permissionsAwareManagement)
}

object AuthHttpConfig {
Expand Down
1 change: 0 additions & 1 deletion auth/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ GET /cookieMonster auth.AuthController.cookieMonster
# Management
GET /management/healthcheck com.gu.mediaservice.lib.management.Management.healthCheck
GET /management/manifest com.gu.mediaservice.lib.management.Management.manifest
GET /management/whoAmI com.gu.mediaservice.lib.management.InnerServiceStatusCheckController.whoAmI(depth: Int)

# Shoo robots away
GET /robots.txt com.gu.mediaservice.lib.management.Management.disallowRobots
83 changes: 48 additions & 35 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import play.sbt.PlayImport.PlayKeys._
import com.typesafe.sbt.packager.docker.*
import play.sbt.PlayImport.PlayKeys.*
import sbt.Package.FixedTimestamp

import scala.sys.process._
import scala.collection.JavaConverters.*
import scala.sys.process.*
import scala.util.control.NonFatal
import scala.collection.JavaConverters._

import com.typesafe.sbt.packager.debian.JDebPackaging

// We need to keep the timestamps to allow caching headers to work as expected on assets.
// The below should work, but some problem in one of the plugins (possible the play plugin? or sbt-web?) causes
Expand Down Expand Up @@ -53,6 +52,7 @@ val commonSettings = Seq(
"org.scalatestplus" %% "mockito-3-4" % "3.1.4.0" % Test,
"org.mockito" % "mockito-core" % "2.18.0" % Test,
"org.scalamock" %% "scalamock" % "5.1.0" % Test,
"org.testcontainers" % "localstack" % "1.21.4" % Test
),
dependencyOverrides ++= jacksonOverrides,

Expand Down Expand Up @@ -91,6 +91,7 @@ val maybeBBCLib: Option[sbt.ProjectReference] = if(bbcBuildProcess) Some(bbcProj

lazy val commonLib = project("common-lib").settings(
libraryDependencies ++= Seq(
"com.google.guava" % "guava" % "33.5.0-jre",
"com.gu" %% "editorial-permissions-client" % "7.0.0",
"com.gu" %% "pan-domain-auth-play_3-0" % "19.0.0",
"software.amazon.awssdk" % "iam" % awsSdkV2Version,
Expand Down Expand Up @@ -124,7 +125,7 @@ lazy val commonLib = project("common-lib").settings(
"software.amazon.awssdk" % "bedrockruntime" % awsSdkV2Version,
"software.amazon.awssdk" % "s3vectors" % awsSdkV2Version,
ws,
"org.testcontainers" % "elasticsearch" % "1.21.4" % Test,
"org.testcontainers" % "testcontainers-elasticsearch" % "2.0.2" % Test,
),
dependencyOverrides += "ch.qos.logback" % "logback-classic" % "1.2.13" % Test
)
Expand All @@ -141,9 +142,9 @@ lazy val auth = playProject("auth", 9011)

lazy val collections = playProject("collections", 9010)

lazy val cropper = playProject("cropper", 9006)
lazy val cropper = playImageLoaderProject("cropper", 9006)

lazy val imageLoader = playProject("image-loader", 9003).settings {
lazy val imageLoader = playImageLoaderProject("image-loader", 9003).settings {
libraryDependencies ++= Seq(
"org.apache.tika" % "tika-core" % "3.2.3",
"com.drewnoakes" % "metadata-extractor" % "2.19.0"
Expand Down Expand Up @@ -180,7 +181,7 @@ lazy val thrall = playProject("thrall", 9002)
"software.amazon.awssdk" % "kinesis" % awsSdkV2Version,
"software.amazon.awssdk" % "dynamodb" % awsSdkV2Version,
"com.gu" %% "kcl-pekko-stream" % "0.1.2",
"org.testcontainers" % "elasticsearch" % "1.19.2" % Test,
"org.testcontainers" % "testcontainers-elasticsearch" % "2.0.2" % Test,
"com.google.protobuf" % "protobuf-java" % "3.19.6"
),
dependencyOverrides ++= Seq(
Expand All @@ -190,8 +191,6 @@ lazy val thrall = playProject("thrall", 9002)

lazy val usage = playProject("usage", 9009).settings(
libraryDependencies ++= Seq(
"com.gu" %% "content-api-client-default" % "32.0.0",
"com.gu" %% "content-api-client-aws" % "1.0.1",
"io.reactivex" %% "rxscala" % "0.27.0",
"software.amazon.kinesis" % "amazon-kinesis-client" % awsKclVersion,
// explicit dependencies on kinesis and dynamodb to upgrade the versions used by kcl
Expand Down Expand Up @@ -241,39 +240,53 @@ val buildInfo = Seq(
)

def playProject(projectName: String, port: Int, path: Option[String] = None): Project = {
val commonProject = project(projectName, path)
.enablePlugins(PlayScala, JDebPackaging, SystemdPlugin, BuildInfoPlugin)
project(projectName, path)
.enablePlugins(PlayScala, BuildInfoPlugin, DockerPlugin)
.dependsOn(restLib)
.settings(commonSettings ++ buildInfo ++ Seq(
dockerBaseImage := "eclipse-temurin:11",
dockerExposedPorts := Seq(port),
playDefaultPort := port,
debianPackageDependencies := Seq("java11-runtime-headless"),
Linux / maintainer := "Guardian Developers <dig.dev.software@theguardian.com>",
Linux / packageSummary := description.value,
packageDescription := description.value,

bashScriptEnvConfigLocation := Some("/etc/environment"),
Debian / makeEtcDefault := None,
Debian / packageBin := {
val originalFileName = (Debian / packageBin).value
val (base, ext) = originalFileName.baseAndExt
val newBase = base.replace(s"_${version.value}_all","")
val newFileName = file(originalFileName.getParent) / s"$newBase.$ext"
IO.move(originalFileName, newFileName)
println(s"Renamed $originalFileName to $newFileName")
newFileName
},
Universal / mappings ++= Seq(
file("common-lib/src/main/resources/application.conf") -> "conf/application.conf",
file("common-lib/src/main/resources/logback.xml") -> "conf/logback.xml"
),
Universal / javaOptions ++= Seq(
"-Dpidfile.path=/dev/null",
s"-Dconfig.file=/usr/share/$projectName/conf/application.conf",
s"-Dlogger.file=/usr/share/$projectName/conf/logback.xml",
"-J-Xlog:gc*",
s"-J-Xlog:gc:/var/log/$projectName/gc.log"
)
))
//Add the BBC library dependency if defined
maybeBBCLib.fold(commonProject){commonProject.dependsOn(_)}
s"-Dconfig.file=/opt/docker/conf/application.conf",
s"-Dlogger.file=/opt/docker/conf/logback.xml",
"-XX:+PrintCommandLineFlags", "-XX:MaxRAMPercentage=40"
))
)
}

def playImageLoaderProject(projectName: String, port: Int, path: Option[String] = None): Project = {
project(projectName, path)
.enablePlugins(PlayScala, BuildInfoPlugin, DockerPlugin)
.dependsOn(restLib)
.settings(commonSettings ++ buildInfo ++ Seq(
dockerBaseImage := "eu.gcr.io/grid-301122/jdk-vips:25-8.18.4",
dockerExposedPorts := Seq(port),
dockerCommands ++= Seq(
Cmd("ENV", "LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so")
),
playDefaultPort := port,

bashScriptEnvConfigLocation := Some("/etc/environment"),
Universal / mappings ++= Seq(
file("common-lib/src/main/resources/application.conf") -> "conf/application.conf",
file("common-lib/src/main/resources/logback.xml") -> "conf/logback.xml",
file("image-loader/cmyk.icc") -> "cmyk.icc",
file("image-loader/facebook-TINYsRGB_c2.icc") -> "facebook-TINYsRGB_c2.icc",
file("image-loader/grayscale.icc") -> "grayscale.icc",
file("image-loader/srgb.icc") -> "srgb.icc"
),
Universal / javaOptions ++= Seq(
"-Dpidfile.path=/dev/null",
s"-Dconfig.file=/opt/docker/conf/application.conf",
s"-Dlogger.file=/opt/docker/conf/logback.xml",
"-XX:+PrintCommandLineFlags"
)))
}
68 changes: 68 additions & 0 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
options:
machineType: 'N1_HIGHCPU_8'
steps:
- name: 'node:24-alpine'
entrypoint: 'npm'
dir: 'kahuna'
args: [ 'install' ]
- name: 'node:24-alpine'
entrypoint: 'npm'
dir: 'kahuna'
args: [ 'run', 'test' ]
- name: 'node:24-alpine'
entrypoint: 'npm'
dir: 'kahuna'
args: [ 'run', 'dist' ]

- name: 'gcr.io/$PROJECT_ID/scala-sbt:1.6.2-jdk-11'
args: ['docker:publishLocal']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'auth:0.1', 'eu.gcr.io/$PROJECT_ID/auth:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/auth:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'cropper:0.1', 'eu.gcr.io/$PROJECT_ID/cropper:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/cropper:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'collections:0.1', 'eu.gcr.io/$PROJECT_ID/collections:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/collections:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'image-loader:0.1', 'eu.gcr.io/$PROJECT_ID/image-loader:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/image-loader:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'kahuna:0.1', 'eu.gcr.io/$PROJECT_ID/kahuna:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/kahuna:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'leases:0.1', 'eu.gcr.io/$PROJECT_ID/leases:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/leases:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'media-api:0.1', 'eu.gcr.io/$PROJECT_ID/media-api:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/media-api:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'metadata-editor:0.1', 'eu.gcr.io/$PROJECT_ID/metadata-editor:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/metadata-editor:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'thrall:0.1', 'eu.gcr.io/$PROJECT_ID/thrall:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/thrall:$BRANCH_NAME']

- name: 'gcr.io/cloud-builders/docker'
args: ['tag', 'usage:0.1', 'eu.gcr.io/$PROJECT_ID/usage:$BRANCH_NAME']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'eu.gcr.io/$PROJECT_ID/usage:$BRANCH_NAME']
9 changes: 4 additions & 5 deletions collections/app/CollectionsComponents.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import com.gu.mediaservice.lib.management.InnerServiceStatusCheckController
import com.gu.mediaservice.lib.play.GridComponents
import controllers.{CollectionsController, ImageCollectionsController}
import lib.{CollectionsConfig, CollectionsMetrics, Notifications}
import play.api.ApplicationLoader.Context
import router.Routes
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
import store.{CollectionsStore, ImageCollectionsStore}

class CollectionsComponents(context: Context) extends GridComponents(context, new CollectionsConfig(_)) {
final override val buildInfo = utils.buildinfo.BuildInfo

val collectionsStore = new CollectionsStore(config)
val imageCollectionsStore = new ImageCollectionsStore(config)
private val collectionsStore = new CollectionsStore(config.collectionsTable, config.withAWSCredentials(DynamoDbAsyncClient.builder()).build())
val imageCollectionsStore = new ImageCollectionsStore(config.imageCollectionsTable, config.withAWSCredentials(DynamoDbAsyncClient.builder()).build())
val metrics = new CollectionsMetrics(config, actorSystem, applicationLifecycle)
val notifications = new Notifications(config)

val collections = new CollectionsController(auth, config, collectionsStore, controllerComponents)
val imageCollections = new ImageCollectionsController(auth, config, notifications, imageCollectionsStore, controllerComponents)
val InnerServiceStatusCheckController = new InnerServiceStatusCheckController(auth, controllerComponents, config.services, wsClient)


override val router = new Routes(httpErrorHandler, collections, imageCollections, management, InnerServiceStatusCheckController)
override val router = new Routes(httpErrorHandler, collections, imageCollections, management)
}
5 changes: 1 addition & 4 deletions collections/app/store/CollectionsStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package store

import com.gu.mediaservice.lib.collections.CollectionsManager
import com.gu.mediaservice.model.{ActionData, Collection}
import lib.CollectionsConfig
import org.joda.time.DateTime
import org.scanamo.generic.auto.genericDerivedFormat
import org.scanamo.{DynamoFormat, ScanamoAsync, Table}
Expand All @@ -16,9 +15,7 @@ import org.scanamo.generic.semiauto.FieldName

case class Record(id: String, collection: Collection)

class CollectionsStore(config: CollectionsConfig) extends DynamoHelpers {
override val tableName: FieldName = config.collectionsTable
lazy val client: DynamoDbAsyncClient = config.withAWSCredentials(DynamoDbAsyncClient.builder()).build()
class CollectionsStore(val tableName: String, client: DynamoDbAsyncClient) extends DynamoHelpers {
import org.scanamo.generic.semiauto._
implicit val dateTimeFormat: Typeclass[DateTime] =
DynamoFormat.coercedXmap[DateTime, String, IllegalArgumentException](DateTime.parse, _.toString)
Expand Down
5 changes: 1 addition & 4 deletions collections/app/store/ImageCollectionsStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import scala.concurrent.ExecutionContext.Implicits.global

case class ImageRecord(id: String, collections: List[Collection])

class ImageCollectionsStore(config: CollectionsConfig) extends DynamoHelpers {

override val tableName = config.imageCollectionsTable
lazy val client: DynamoDbAsyncClient = config.withAWSCredentials(DynamoDbAsyncClient.builder()).build()
class ImageCollectionsStore(val tableName: String, val client: DynamoDbAsyncClient) extends DynamoHelpers {

import org.scanamo.generic.semiauto._
implicit val dateTimeFormat: Typeclass[DateTime] =
Expand Down
1 change: 0 additions & 1 deletion collections/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ POST /corrected-collections controllers.CollectionsC
# Management
GET /management/healthcheck com.gu.mediaservice.lib.management.Management.healthCheck
GET /management/manifest com.gu.mediaservice.lib.management.Management.manifest
GET /management/whoAmI com.gu.mediaservice.lib.management.InnerServiceStatusCheckController.whoAmI(depth: Int)

# Shoo robots away
GET /robots.txt com.gu.mediaservice.lib.management.Management.disallowRobots
Loading
Loading