From 89295fa4f04e8ad301b91ba61b2cd8d929ace19c Mon Sep 17 00:00:00 2001 From: Mariot Chauvin Date: Tue, 19 Nov 2024 19:04:51 +0000 Subject: [PATCH] Upgrade to SDK v2 --- .../scala/com/gu/anghammarad/Config.scala | 32 ++++++----- .../anghammarad/messages/EmailService.scala | 33 ++++++------ build.sbt | 11 ++-- .../main/scala/com/gu/anghammarad/AWS.scala | 54 +++++++++---------- .../com/gu/anghammarad/Anghammarad.scala | 41 +++++++------- 5 files changed, 90 insertions(+), 81 deletions(-) diff --git a/anghammarad/src/main/scala/com/gu/anghammarad/Config.scala b/anghammarad/src/main/scala/com/gu/anghammarad/Config.scala index 4630859..c6d3e9b 100644 --- a/anghammarad/src/main/scala/com/gu/anghammarad/Config.scala +++ b/anghammarad/src/main/scala/com/gu/anghammarad/Config.scala @@ -1,10 +1,13 @@ package com.gu.anghammarad -import com.amazonaws.auth._ -import com.amazonaws.auth.profile.ProfileCredentialsProvider -import com.amazonaws.regions.Regions -import com.amazonaws.services.s3.AmazonS3Client -import com.amazonaws.services.s3.model.{GetObjectRequest, S3ObjectInputStream} +import software.amazon.awssdk.auth._ +import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain +import software.amazon.awssdk.auth.credentials.{EnvironmentVariableCredentialsProvider, ProfileCredentialsProvider} +import software.amazon.awssdk.core.ResponseInputStream +import software.amazon.awssdk.core.sync.ResponseTransformer +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.{GetObjectRequest, GetObjectResponse} import com.gu.anghammarad.common.AnghammaradException.Fail import scala.io.Source @@ -12,19 +15,19 @@ import scala.util.{Success, Try} object Config { - val credentialsProvider = new AWSCredentialsProviderChain( - new ProfileCredentialsProvider("deployTools"), - new EnvironmentVariableCredentialsProvider() + val credentialsProvider = AwsCredentialsProviderChain.of( + ProfileCredentialsProvider.create("deployTools"), + EnvironmentVariableCredentialsProvider.create() ) - private val s3Client = AmazonS3Client + private val s3Client = S3Client .builder - .withCredentials(credentialsProvider) - .withRegion(Regions.EU_WEST_1) + .credentialsProvider(credentialsProvider) + .region(Region.EU_WEST_1) .build() - private def fetchContent(request: GetObjectRequest): Try[S3ObjectInputStream] = { - Try(s3Client.getObject(request).getObjectContent) + private def fetchContent(request: GetObjectRequest): Try[ResponseInputStream[GetObjectResponse]] = { + Try(s3Client.getObject(request, ResponseTransformer.toInputStream())) } private def fetchString(request: GetObjectRequest): Try[String] = { @@ -46,7 +49,8 @@ object Config { val bucket = s"anghammarad-configuration" val key = s"$stage/anghammarad-config.json" - val request = new GetObjectRequest(bucket, key) + val request = GetObjectRequest.builder().key(key).bucket(bucket).build(); + fetchString(request) } } diff --git a/anghammarad/src/main/scala/com/gu/anghammarad/messages/EmailService.scala b/anghammarad/src/main/scala/com/gu/anghammarad/messages/EmailService.scala index 7b3043b..6b1097a 100644 --- a/anghammarad/src/main/scala/com/gu/anghammarad/messages/EmailService.scala +++ b/anghammarad/src/main/scala/com/gu/anghammarad/messages/EmailService.scala @@ -1,8 +1,8 @@ package com.gu.anghammarad.messages -import com.amazonaws.regions.Regions -import com.amazonaws.services.simpleemail.model.{Body, Content, Destination, SendEmailRequest, Message => AwsMessage} -import com.amazonaws.services.simpleemail.{AmazonSimpleEmailService, AmazonSimpleEmailServiceClientBuilder} +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.ses.model.{Body, Content, Destination, SendEmailRequest, Message => AwsMessage} +import software.amazon.awssdk.services.ses.SesClient import com.gu.anghammarad.models.EmailMessage import com.gu.anghammarad.Config @@ -10,24 +10,27 @@ import scala.util.Try object EmailService { - val client: AmazonSimpleEmailService = AmazonSimpleEmailServiceClientBuilder.standard().withRegion(Regions.EU_WEST_1) - .withCredentials(Config.credentialsProvider) + val client = SesClient.builder().region(Region.EU_WEST_1) + .credentialsProvider(Config.credentialsProvider) .build() def emailRequest(senderAddress: String, recipientAddress: String, message: EmailMessage): SendEmailRequest = { - def buildContent(data: String) = new Content().withCharset("UTF-8").withData(data) + def buildContent(data: String) = Content.builder().charset("UTF-8").data(data).build() - val awsMessage = new AwsMessage() - .withSubject(buildContent(message.subject)) - .withBody(new Body() - .withHtml(buildContent(message.html)) - .withText(buildContent(message.plainText)) + val awsMessage = AwsMessage.builder() + .subject(buildContent(message.subject)) + .body(Body.builder() + .html(buildContent(message.html)) + .text(buildContent(message.plainText)) + .build() ) + .build() - new SendEmailRequest() - .withDestination(new Destination().withToAddresses(recipientAddress)) - .withSource(senderAddress) - .withMessage(awsMessage) + SendEmailRequest.builder() + .destination(Destination.builder().toAddresses(recipientAddress).build()) + .source(senderAddress) + .message(awsMessage) + .build() } def sendEmail(senderAddress: String, recipientAddress: String, message: EmailMessage): Try[Unit] = { Try(client.sendEmail(emailRequest(senderAddress, recipientAddress, message))) diff --git a/build.sbt b/build.sbt index 6f93189..6053df3 100644 --- a/build.sbt +++ b/build.sbt @@ -24,7 +24,7 @@ inThisBuild(Seq( licenses := Seq(License.Apache2), )) -val awsSdkVersion = "1.12.777" +val awsSdkVersion = "2.29.15" val circeVersion = "0.14.10" val flexmarkVersion = "0.64.8" val scalaTestVersion = "3.2.19" @@ -67,7 +67,8 @@ lazy val client = project .settings( name := "anghammarad-client", libraryDependencies ++= Seq( - "com.amazonaws" % "aws-java-sdk-sns" % awsSdkVersion, + "software.amazon.awssdk" % "sns" % awsSdkVersion, + "org.json" % "json" % "20240303", "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion, "org.scalatest" %% "scalatest" % scalaTestVersion % Test @@ -84,9 +85,9 @@ lazy val anghammarad = project "org.scala-lang.modules" %% "scala-collection-compat" % "2.12.0", "com.amazonaws" % "aws-lambda-java-events" % "3.14.0", "com.amazonaws" % "aws-lambda-java-core" % "1.2.3", - "com.amazonaws" % "aws-java-sdk-lambda" % awsSdkVersion, - "com.amazonaws" % "aws-java-sdk-ses" % awsSdkVersion, - "com.amazonaws" % "aws-java-sdk-s3" % awsSdkVersion, + "software.amazon.awssdk" % "lambda" % awsSdkVersion, + "software.amazon.awssdk" % "ses" % awsSdkVersion, + "software.amazon.awssdk" % "s3" % awsSdkVersion, "io.circe" %% "circe-core" % circeVersion, "io.circe" %% "circe-generic" % circeVersion, "io.circe" %% "circe-parser" % circeVersion, diff --git a/client/src/main/scala/com/gu/anghammarad/AWS.scala b/client/src/main/scala/com/gu/anghammarad/AWS.scala index 9a2f981..2e5ee79 100644 --- a/client/src/main/scala/com/gu/anghammarad/AWS.scala +++ b/client/src/main/scala/com/gu/anghammarad/AWS.scala @@ -1,11 +1,14 @@ package com.gu.anghammarad -import com.amazonaws.AmazonWebServiceRequest -import com.amazonaws.auth.profile.ProfileCredentialsProvider -import com.amazonaws.auth.{AWSCredentialsProviderChain, EnvironmentVariableCredentialsProvider, InstanceProfileCredentialsProvider} -import com.amazonaws.handlers.AsyncHandler -import com.amazonaws.regions.Regions -import com.amazonaws.services.sns.{AmazonSNSAsync, AmazonSNSAsyncClientBuilder} + +import java.util.concurrent.CompletableFuture + +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sns.SnsAsyncClient +import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain +import software.amazon.awssdk.auth.credentials.{EnvironmentVariableCredentialsProvider, ProfileCredentialsProvider, InstanceProfileCredentialsProvider} + + import scala.concurrent.{Future, Promise} @@ -14,36 +17,31 @@ object AWS { /** * Use this to make an SNS client, or provide your own. */ - def snsClient(credentialsProvider: AWSCredentialsProviderChain): AmazonSNSAsync = { - AmazonSNSAsyncClientBuilder.standard() - .withRegion(Regions.EU_WEST_1) - .withCredentials(credentialsProvider) + def snsClient(credentialsProvider: AwsCredentialsProviderChain): SnsAsyncClient = { + SnsAsyncClient.builder() + .region(Region.EU_WEST_1) + .credentialsProvider(credentialsProvider) .build() } - def credentialsProvider(): AWSCredentialsProviderChain = { - new AWSCredentialsProviderChain( + def credentialsProvider(): AwsCredentialsProviderChain = { + AwsCredentialsProviderChain.of( // EC2 - InstanceProfileCredentialsProvider.getInstance(), + InstanceProfileCredentialsProvider.create(), // Lambda - new EnvironmentVariableCredentialsProvider(), + EnvironmentVariableCredentialsProvider.create(), // local - new ProfileCredentialsProvider("deployTools") + ProfileCredentialsProvider.create("deployTools"), ) } - private class AwsAsyncPromiseHandler[R <: AmazonWebServiceRequest, T](promise: Promise[T]) extends AsyncHandler[R, T] { - def onError(e: Exception): Unit = { - promise failure e - } - def onSuccess(r: R, t: T): Unit = { - promise success t - } + private[anghammarad] def asScala[T](cf: CompletableFuture[T]): Future[T] = { + val p = Promise[T]() + cf.whenCompleteAsync{ (result, ex) => + if (result == null) p failure ex + else p success result + } + p.future } - private[anghammarad] def awsToScala[R <: AmazonWebServiceRequest, T](sdkMethod: ( (R, AsyncHandler[R, T]) => java.util.concurrent.Future[T])): (R => Future[T]) = { req => - val p = Promise[T]() - sdkMethod(req, new AwsAsyncPromiseHandler(p)) - p.future - } -} +} \ No newline at end of file diff --git a/client/src/main/scala/com/gu/anghammarad/Anghammarad.scala b/client/src/main/scala/com/gu/anghammarad/Anghammarad.scala index 3e0ef9b..95d3a06 100644 --- a/client/src/main/scala/com/gu/anghammarad/Anghammarad.scala +++ b/client/src/main/scala/com/gu/anghammarad/Anghammarad.scala @@ -1,10 +1,10 @@ package com.gu.anghammarad -import com.amazonaws.services.sns.AmazonSNSAsync -import com.amazonaws.services.sns.model.PublishRequest import com.gu.anghammarad.AWS._ import com.gu.anghammarad.Json._ import com.gu.anghammarad.models._ +import software.amazon.awssdk.services.sns.SnsAsyncClient +import software.amazon.awssdk.services.sns.model.PublishRequest import scala.concurrent.{ExecutionContext, Future} @@ -25,13 +25,14 @@ object Anghammarad { * @return Future containing the resulting SNS Message ID */ def notify(subject: String, message: String, actions: List[Action], target: List[Target], channel: RequestedChannel, - sourceSystem: String, topicArn: String, client: AmazonSNSAsync = defaultClient) + sourceSystem: String, topicArn: String, client: SnsAsyncClient = defaultClient) (implicit executionContext: ExecutionContext): Future[String] = { - val request = new PublishRequest() - .withTopicArn(topicArn) - .withSubject(subject) - .withMessage(messageJson(message, sourceSystem, channel, target, actions)) - awsToScala(client.publishAsync)(request).map(_.getMessageId) + val request = PublishRequest.builder() + .topicArn(topicArn) + .subject(subject) + .message(messageJson(message, sourceSystem, channel, target, actions)) + .build() + asScala(client.publish(request)).map(_.messageId) } /** @@ -44,11 +45,12 @@ object Anghammarad { */ def notify(notification: Notification, topicArn: String) (implicit executionContext: ExecutionContext): Future[String] = { - val request = new PublishRequest() - .withTopicArn(topicArn) - .withSubject(notification.subject) - .withMessage(messageJson(notification.message, notification.sourceSystem, notification.channel, notification.target, notification.actions)) - awsToScala(defaultClient.publishAsync)(request).map(_.getMessageId) + val request = PublishRequest.builder() + .topicArn(topicArn) + .subject(notification.subject) + .message(messageJson(notification.message, notification.sourceSystem, notification.channel, notification.target, notification.actions)) + .build() + asScala(defaultClient.publish(request)).map(_.messageId) } /** @@ -59,12 +61,13 @@ object Anghammarad { * @param client The SNS client used to add your notification to the topic * @return Future containing the resulting SNS Message ID */ - def notify(notification: Notification, topicArn: String, client: AmazonSNSAsync) + def notify(notification: Notification, topicArn: String, client: SnsAsyncClient) (implicit executionContext: ExecutionContext): Future[String] = { - val request = new PublishRequest() - .withTopicArn(topicArn) - .withSubject(notification.subject) - .withMessage(messageJson(notification.message, notification.sourceSystem, notification.channel, notification.target, notification.actions)) - awsToScala(client.publishAsync)(request).map(_.getMessageId) + val request = PublishRequest.builder() + .topicArn(topicArn) + .subject(notification.subject) + .message(messageJson(notification.message, notification.sourceSystem, notification.channel, notification.target, notification.actions)) + .build() + asScala(client.publish(request)).map(_.messageId) } }