mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-29 13:27:43 +00:00
Add SNS Notifier (#760)
Changes in this pull request: - Notify SNS topic on batch completion
This commit is contained in:
parent
07dfbe091d
commit
e51261ba2c
@ -15,7 +15,7 @@
|
|||||||
# Note: this will not remove the actual images from your
|
# Note: this will not remove the actual images from your
|
||||||
# machine, just the running containers
|
# machine, just the running containers
|
||||||
|
|
||||||
IDS=$(docker ps -a | grep -e 'mysql:5.7' -e 'cnadiminti/dynamodb-local:2017-02-16' -e 's12v/elasticmq:0.13.8' -e 'vinyldns' -e 'flaviovs/mock-smtp' | awk '{print $1}')
|
IDS=$(docker ps -a | grep -e 'mysql:5.7' -e 'cnadiminti/dynamodb-local:2017-02-16' -e 's12v/elasticmq:0.13.8' -e 'vinyldns' -e 'flaviovs/mock-smtp' -e 'localstack/localstack' | awk '{print $1}')
|
||||||
|
|
||||||
echo "killing..."
|
echo "killing..."
|
||||||
echo $(echo "$IDS" | xargs -I {} docker kill {})
|
echo $(echo "$IDS" | xargs -I {} docker kill {})
|
||||||
|
@ -33,6 +33,18 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./elasticmq/custom.conf:/etc/elasticmq/elasticmq.conf
|
- ./elasticmq/custom.conf:/etc/elasticmq/elasticmq.conf
|
||||||
|
|
||||||
|
localstack:
|
||||||
|
image: localstack/localstack
|
||||||
|
ports:
|
||||||
|
- "19006:19006"
|
||||||
|
- "19007:19007"
|
||||||
|
environment:
|
||||||
|
- SERVICES=sns:19006,sqs:19007
|
||||||
|
- START_WEB=0
|
||||||
|
- DOCKER_HOST=unix:///var/run/docker.sock
|
||||||
|
volumes:
|
||||||
|
- "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
|
||||||
|
|
||||||
mail:
|
mail:
|
||||||
image: flaviovs/mock-smtp
|
image: flaviovs/mock-smtp
|
||||||
ports:
|
ports:
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.notifier.sns
|
||||||
|
|
||||||
|
import com.typesafe.config.{Config, ConfigFactory}
|
||||||
|
import vinyldns.core.notifier._
|
||||||
|
import vinyldns.api.MySqlApiIntegrationSpec
|
||||||
|
import vinyldns.mysql.MySqlIntegrationSpec
|
||||||
|
import org.scalatest.{Matchers, WordSpecLike}
|
||||||
|
import vinyldns.core.domain.batch._
|
||||||
|
import vinyldns.core.domain.record.RecordType
|
||||||
|
import vinyldns.core.domain.record.AData
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import vinyldns.core.TestMembershipData._
|
||||||
|
import cats.effect.IO
|
||||||
|
import com.amazonaws.services.sns.AmazonSNSClientBuilder
|
||||||
|
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration
|
||||||
|
import com.amazonaws.services.sqs.AmazonSQSClientBuilder
|
||||||
|
import org.json4s.jackson.JsonMethods._
|
||||||
|
import org.json4s.DefaultFormats
|
||||||
|
import com.amazonaws.auth.BasicAWSCredentials
|
||||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider
|
||||||
|
|
||||||
|
class SnsNotifierIntegrationSpec
|
||||||
|
extends MySqlApiIntegrationSpec
|
||||||
|
with MySqlIntegrationSpec
|
||||||
|
with Matchers
|
||||||
|
with WordSpecLike {
|
||||||
|
|
||||||
|
import vinyldns.api.domain.DomainValidations._
|
||||||
|
|
||||||
|
implicit val formats = DefaultFormats
|
||||||
|
|
||||||
|
val snsConfig: Config = ConfigFactory.load().getConfig("vinyldns.sns.settings")
|
||||||
|
|
||||||
|
"Sns Notifier" should {
|
||||||
|
|
||||||
|
"send a notification" in {
|
||||||
|
val batchChange = BatchChange(
|
||||||
|
okUser.id,
|
||||||
|
okUser.userName,
|
||||||
|
None,
|
||||||
|
DateTime.parse("2019-07-22T19:38:23Z"),
|
||||||
|
List(
|
||||||
|
SingleAddChange(
|
||||||
|
Some("some-zone-id"),
|
||||||
|
Some("zone-name"),
|
||||||
|
Some("record-name"),
|
||||||
|
"a" * HOST_MAX_LENGTH,
|
||||||
|
RecordType.A,
|
||||||
|
300,
|
||||||
|
AData("1.1.1.1"),
|
||||||
|
SingleChangeStatus.Complete,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None
|
||||||
|
)),
|
||||||
|
approvalStatus = BatchChangeApprovalStatus.AutoApproved,
|
||||||
|
id = "a615e2bb-8b35-4a39-8947-1edd0e653afa"
|
||||||
|
)
|
||||||
|
|
||||||
|
val credentialsProvider = new AWSStaticCredentialsProvider(
|
||||||
|
new BasicAWSCredentials(
|
||||||
|
snsConfig.getString("access-key"),
|
||||||
|
snsConfig.getString("secret-key")))
|
||||||
|
val sns = AmazonSNSClientBuilder.standard
|
||||||
|
.withEndpointConfiguration(
|
||||||
|
new EndpointConfiguration(
|
||||||
|
snsConfig.getString("service-endpoint"),
|
||||||
|
snsConfig.getString("signing-region")))
|
||||||
|
.withCredentials(credentialsProvider)
|
||||||
|
.build()
|
||||||
|
val sqs = AmazonSQSClientBuilder
|
||||||
|
.standard()
|
||||||
|
.withEndpointConfiguration(
|
||||||
|
new EndpointConfiguration("http://127.0.0.1:19007", "us-east-1")
|
||||||
|
)
|
||||||
|
.withCredentials(credentialsProvider)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val program = for {
|
||||||
|
queueUrl <- IO { sqs.createQueue("batchChanges").getQueueUrl }
|
||||||
|
topic <- IO { sns.createTopic("batchChanges").getTopicArn }
|
||||||
|
_ <- IO { sns.subscribe(topic, "sqs", queueUrl) }
|
||||||
|
notifier <- new SnsNotifierProvider()
|
||||||
|
.load(NotifierConfig("", snsConfig), userRepository)
|
||||||
|
_ <- notifier.notify(Notification(batchChange))
|
||||||
|
messages <- IO { sqs.receiveMessage(queueUrl).getMessages }
|
||||||
|
_ <- IO {
|
||||||
|
sns.deleteTopic(topic)
|
||||||
|
sqs.deleteQueue(queueUrl)
|
||||||
|
}
|
||||||
|
} yield messages
|
||||||
|
|
||||||
|
val messages = program.unsafeRunSync()
|
||||||
|
|
||||||
|
messages.size should be(1)
|
||||||
|
|
||||||
|
val notification = parse(messages.get(0).getBody)
|
||||||
|
(notification \ "Message").extract[String] should be(
|
||||||
|
"""{"userId":"ok","userName":"ok","createdTimestamp":"2019-07-22T19:38:23Z",""" +
|
||||||
|
""""status":"Complete","id":"a615e2bb-8b35-4a39-8947-1edd0e653afa"}""")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -98,6 +98,17 @@ vinyldns {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sns {
|
||||||
|
class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider"
|
||||||
|
settings {
|
||||||
|
topic-arn = "arn:aws:sns:us-east-1:000000000000:batchChanges"
|
||||||
|
access-key = "vinyldnsTest"
|
||||||
|
secret-key = "notNeededForSnsLocal"
|
||||||
|
service-endpoint = "http://127.0.0.1:19006"
|
||||||
|
signing-region = "us-east-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultZoneConnection {
|
defaultZoneConnection {
|
||||||
name = "vinyldns."
|
name = "vinyldns."
|
||||||
keyName = "vinyldns."
|
keyName = "vinyldns."
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.notifier.sns
|
||||||
|
|
||||||
|
import vinyldns.core.notifier.{Notification, Notifier}
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.syntax.functor._
|
||||||
|
import vinyldns.core.domain.batch.{BatchChange, BatchChangeInfo}
|
||||||
|
import vinyldns.api.route.VinylDNSJsonProtocol
|
||||||
|
import org.json4s.jackson.JsonMethods._
|
||||||
|
import com.amazonaws.services.sns.AmazonSNS
|
||||||
|
import com.amazonaws.services.sns.model.PublishRequest
|
||||||
|
import com.amazonaws.services.sns.model.MessageAttributeValue
|
||||||
|
import org.json4s.JsonAST.JNull
|
||||||
|
|
||||||
|
class SnsNotifier(config: SnsNotifierConfig, sns: AmazonSNS)
|
||||||
|
extends Notifier
|
||||||
|
with VinylDNSJsonProtocol {
|
||||||
|
|
||||||
|
def notify(notification: Notification[_]): IO[Unit] =
|
||||||
|
notification.change match {
|
||||||
|
case bc: BatchChange => sendBatchChangeNotification(BatchChangeInfo(bc))
|
||||||
|
case _ => IO.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
def sendBatchChangeNotification(bc: BatchChangeInfo): IO[Unit] =
|
||||||
|
IO {
|
||||||
|
val message =
|
||||||
|
compact(
|
||||||
|
render(BatchChangeInfoSerializer.toJson(bc).replace(List("changes"), JNull)).noNulls)
|
||||||
|
val request = new PublishRequest(config.topicArn, message)
|
||||||
|
request.addMessageAttributesEntry(
|
||||||
|
"userName",
|
||||||
|
new MessageAttributeValue().withDataType("String").withStringValue(bc.userName))
|
||||||
|
sns.publish(request)
|
||||||
|
}.void
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.notifier.sns
|
||||||
|
|
||||||
|
case class SnsNotifierConfig(
|
||||||
|
topicArn: String,
|
||||||
|
serviceEndpoint: String,
|
||||||
|
signingRegion: String,
|
||||||
|
accessKey: String,
|
||||||
|
secretKey: String
|
||||||
|
)
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.notifier.sns
|
||||||
|
|
||||||
|
import vinyldns.core.notifier.{Notifier, NotifierConfig, NotifierProvider}
|
||||||
|
import vinyldns.core.domain.membership.UserRepository
|
||||||
|
import pureconfig.module.catseffect.loadConfigF
|
||||||
|
import cats.effect.IO
|
||||||
|
import com.amazonaws.services.sns.AmazonSNS
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import com.amazonaws.services.sns.AmazonSNSClientBuilder
|
||||||
|
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration
|
||||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider
|
||||||
|
import com.amazonaws.auth.BasicAWSCredentials
|
||||||
|
|
||||||
|
class SnsNotifierProvider extends NotifierProvider {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[SnsNotifierProvider])
|
||||||
|
|
||||||
|
def load(config: NotifierConfig, userRepository: UserRepository): IO[Notifier] =
|
||||||
|
for {
|
||||||
|
snsConfig <- loadConfigF[IO, SnsNotifierConfig](config.settings)
|
||||||
|
client <- createClient(snsConfig)
|
||||||
|
} yield new SnsNotifier(snsConfig, client)
|
||||||
|
|
||||||
|
def createClient(config: SnsNotifierConfig): IO[AmazonSNS] = IO {
|
||||||
|
logger.error(
|
||||||
|
"Setting up sns notifier client with settings: " +
|
||||||
|
s"service endpoint: ${config.serviceEndpoint}; " +
|
||||||
|
s"signing region: ${config.signingRegion}; " +
|
||||||
|
s"topic name: ${config.topicArn}")
|
||||||
|
AmazonSNSClientBuilder.standard
|
||||||
|
.withEndpointConfiguration(
|
||||||
|
new EndpointConfiguration(config.serviceEndpoint, config.signingRegion))
|
||||||
|
.withCredentials(new AWSStaticCredentialsProvider(
|
||||||
|
new BasicAWSCredentials(config.accessKey, config.secretKey)))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.notifier.sns
|
||||||
|
import org.scalatest.{BeforeAndAfterEach, Matchers, WordSpec}
|
||||||
|
import org.scalatest.mockito.MockitoSugar
|
||||||
|
import vinyldns.api.CatsHelpers
|
||||||
|
import vinyldns.core.domain.membership.UserRepository
|
||||||
|
import vinyldns.core.notifier.Notification
|
||||||
|
import org.mockito.Matchers._
|
||||||
|
import org.mockito.Mockito._
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import cats.effect.IO
|
||||||
|
import vinyldns.core.domain.batch.BatchChange
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import vinyldns.core.domain.batch.BatchChangeApprovalStatus
|
||||||
|
import vinyldns.core.domain.batch.SingleChange
|
||||||
|
import vinyldns.core.domain.batch.SingleAddChange
|
||||||
|
import vinyldns.core.domain.batch.SingleDeleteChange
|
||||||
|
import vinyldns.core.domain.record.RecordType
|
||||||
|
import vinyldns.core.domain.record.AData
|
||||||
|
import _root_.vinyldns.core.domain.batch.SingleChangeStatus
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
import vinyldns.core.notifier.NotifierConfig
|
||||||
|
import com.amazonaws.services.sns.AmazonSNS
|
||||||
|
import com.amazonaws.services.sns.model.PublishRequest
|
||||||
|
import com.amazonaws.services.sns.model.PublishResult
|
||||||
|
|
||||||
|
class SnsNotifierSpec
|
||||||
|
extends WordSpec
|
||||||
|
with Matchers
|
||||||
|
with MockitoSugar
|
||||||
|
with BeforeAndAfterEach
|
||||||
|
with CatsHelpers {
|
||||||
|
|
||||||
|
val mockUserRepository = mock[UserRepository]
|
||||||
|
val mockSns = mock[AmazonSNS]
|
||||||
|
|
||||||
|
override protected def beforeEach(): Unit =
|
||||||
|
reset(mockUserRepository, mockSns)
|
||||||
|
|
||||||
|
def batchChange(
|
||||||
|
description: Option[String] = None,
|
||||||
|
changes: List[SingleChange] = List.empty): BatchChange =
|
||||||
|
BatchChange(
|
||||||
|
"test",
|
||||||
|
"testUser",
|
||||||
|
description,
|
||||||
|
DateTime.parse("2019-07-22T17:01:19Z"),
|
||||||
|
changes,
|
||||||
|
None,
|
||||||
|
BatchChangeApprovalStatus.AutoApproved,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"testBatch")
|
||||||
|
|
||||||
|
"Sns Notifier" should {
|
||||||
|
"do nothing for unsupported Notifications" in {
|
||||||
|
val snsConfig: Config = ConfigFactory.parseMap(
|
||||||
|
Map[String, Any](
|
||||||
|
"topic-arn" -> "batches",
|
||||||
|
"service-endpoint" -> "someValue",
|
||||||
|
"signing-region" -> "us-east-1",
|
||||||
|
"access-key" -> "access",
|
||||||
|
"secret-key" -> "secret"
|
||||||
|
).asJava)
|
||||||
|
val notifier = new SnsNotifierProvider()
|
||||||
|
.load(NotifierConfig("", snsConfig), mockUserRepository)
|
||||||
|
.unsafeRunSync()
|
||||||
|
|
||||||
|
notifier.notify(Notification("this won't be supported ever")) should be(IO.unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
"send a notification" in {
|
||||||
|
val notifier = new SnsNotifier(
|
||||||
|
SnsNotifierConfig("batches", "someValue", "us-east-1", "access", "secret"),
|
||||||
|
mockSns
|
||||||
|
)
|
||||||
|
|
||||||
|
val requestArgument = ArgumentCaptor.forClass(classOf[PublishRequest])
|
||||||
|
|
||||||
|
doReturn(new PublishResult()).when(mockSns).publish(requestArgument.capture())
|
||||||
|
|
||||||
|
val description = "notes"
|
||||||
|
val singleChanges: List[SingleChange] = List(
|
||||||
|
SingleAddChange(
|
||||||
|
Some(""),
|
||||||
|
Some(""),
|
||||||
|
Some(""),
|
||||||
|
"www.test.com",
|
||||||
|
RecordType.A,
|
||||||
|
200,
|
||||||
|
AData("1.2.3.4"),
|
||||||
|
SingleChangeStatus.Complete,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
List.empty),
|
||||||
|
SingleDeleteChange(
|
||||||
|
Some(""),
|
||||||
|
Some(""),
|
||||||
|
Some(""),
|
||||||
|
"deleteme.test.com",
|
||||||
|
RecordType.A,
|
||||||
|
SingleChangeStatus.Failed,
|
||||||
|
Some("message for you"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
List.empty)
|
||||||
|
)
|
||||||
|
val change = batchChange(Some(description), singleChanges)
|
||||||
|
|
||||||
|
notifier.notify(Notification(change)).unsafeRunSync()
|
||||||
|
|
||||||
|
val request = requestArgument.getValue()
|
||||||
|
|
||||||
|
request.getTopicArn() should be("batches")
|
||||||
|
val userNameAttribute = request.getMessageAttributes().get("userName")
|
||||||
|
userNameAttribute.getDataType() should be("String")
|
||||||
|
userNameAttribute.getStringValue() should be("testUser")
|
||||||
|
|
||||||
|
request
|
||||||
|
.getMessage() should be("""{"userId":"test","userName":"testUser","comments":"notes",""" +
|
||||||
|
""""createdTimestamp":"2019-07-22T17:01:19Z","status":"PartialFailure","id":"testBatch"}""")
|
||||||
|
|
||||||
|
verify(mockSns).publish(any[PublishRequest])
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -52,7 +52,8 @@ object Dependencies {
|
|||||||
"com.comcast" %% "ip4s-cats" % ip4sV,
|
"com.comcast" %% "ip4s-cats" % ip4sV,
|
||||||
"com.iheart" %% "ficus" % "1.4.3",
|
"com.iheart" %% "ficus" % "1.4.3",
|
||||||
"com.sun.mail" % "javax.mail" % "1.6.2",
|
"com.sun.mail" % "javax.mail" % "1.6.2",
|
||||||
"javax.mail" % "javax.mail-api" % "1.6.2"
|
"javax.mail" % "javax.mail-api" % "1.6.2",
|
||||||
|
"com.amazonaws" % "aws-java-sdk-sns" % awsV withSources()
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val coreDependencies = Seq(
|
lazy val coreDependencies = Seq(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user