2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-22 10:10:12 +00:00

Boot load repos in api (#214)

* conf changes to dynamically load

* have boot dynamically load repos based on config

* update sbt version
This commit is contained in:
Rebecca Star 2018-09-20 10:58:04 -04:00 committed by GitHub
parent 8875a6848e
commit 675cd110d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 490 additions and 526 deletions

View File

@ -18,6 +18,7 @@ cache:
directories: directories:
- $HOME/.ivy2/cache - $HOME/.ivy2/cache
- $HOME/.sbt - $HOME/.sbt
timeout: 900
install: install:
- rvm use 2.2.8 --install --fuzzy - rvm use 2.2.8 --install --fuzzy
@ -26,6 +27,8 @@ install:
before_cache: before_cache:
# Cleanup the cached directories to avoid unnecessary cache updates (https://www.scala-sbt.org/1.0/docs/Travis-CI-with-sbt.html) # Cleanup the cached directories to avoid unnecessary cache updates (https://www.scala-sbt.org/1.0/docs/Travis-CI-with-sbt.html)
- sudo chown -R travis:travis $HOME/.sbt
- sudo chown -R travis:travis $HOME/.ivy2/cache
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
- find $HOME/.sbt -name "*.lock" -print -delete - find $HOME/.sbt -name "*.lock" -print -delete

View File

@ -43,7 +43,7 @@ vinyldns {
type = "vinyldns.core.crypto.NoOpCrypto" type = "vinyldns.core.crypto.NoOpCrypto"
} }
data-stores = ["mysql"] data-stores = ["mysql", "dynamodb"]
mysql { mysql {
settings { settings {
@ -75,69 +75,59 @@ vinyldns {
} }
} }
# default settings point to the docker compose setup dynamodb {
dynamo { settings {
key = "x" # default settings point to the docker compose setup
key = ${?AWS_ACCESS_KEY} key = "x"
secret = "x" key = ${?AWS_ACCESS_KEY}
secret = ${?AWS_SECRET_ACCESS_KEY} secret = "x"
endpoint = "http://vinyldns-dynamodb:8000" secret = ${?AWS_SECRET_ACCESS_KEY}
endpoint = ${?DYNAMODB_ENDPOINT} endpoint = "http://vinyldns-dynamodb:8000"
} endpoint = ${?DYNAMODB_ENDPOINT}
zoneChanges {
dynamo {
tableName = "zoneChange"
provisionedReads = 30
provisionedWrites = 30
} }
}
recordSet { repositories {
dynamo { zone-change {
tableName = "recordSet" table-name = "zoneChange"
provisionedReads = 30 provisioned-reads = 30
provisionedWrites = 30 provisioned-writes = 30
} }
}
recordChange { record-set {
dynamo { table-name = "recordSet"
tableName = "recordChange" provisioned-reads = 30
provisionedReads = 30 provisioned-writes = 30
provisionedWrites = 30 }
}
}
users { record-change {
dynamo { table-name = "recordChange"
tableName = "users" provisioned-reads = 30
provisionedReads = 30 provisioned-writes = 30
provisionedWrites = 30 }
}
}
groups { user {
dynamo { table-name = "users"
tableName = "groups" provisioned-reads = 30
provisionedReads = 30 provisioned-writes = 30
provisionedWrites = 30 }
}
}
groupChanges { group {
dynamo { table-name = "groups"
tableName = "groupChanges" provisioned-reads = 30
provisionedReads = 30 provisioned-writes = 30
provisionedWrites = 30 }
}
}
membership { group-change {
dynamo { table-name = "groupChanges"
tableName = "membership" provisioned-reads = 30
provisionedReads = 30 provisioned-writes = 30
provisionedWrites = 30 }
membership {
table-name = "membership"
provisioned-reads = 30
provisioned-writes = 30
}
} }
} }

View File

@ -31,24 +31,42 @@ vinyldns {
} }
} }
recordSet {
# use the dummy store, this should only be used local
dummy = true
dynamo { dynamodb.repositories {
tableName = "recordSetTest" record-set {
provisionedReads=30 table-name = "recordSetTest"
provisionedWrites=30 provisioned-reads = 30
provisioned-writes = 20
} }
} record-change {
recordChange { table-name = "recordChangeTest"
# use the dummy store, this should only be used local provisioned-reads = 30
dummy = true provisioned-writes = 20
}
dynamo { zone-change {
tableName = "recordChangeTest" table-name = "zoneChangesTest"
provisionedReads = 30 provisioned-reads = 30
provisionedWrites = 30 provisioned-writes = 20
}
user {
table-name = "usersTest"
provisioned-reads = 30
provisioned-writes = 20
}
group {
table-name = "groupsTest"
provisioned-reads = 30
provisioned-writes = 20
}
group-change {
table-name = "groupChangesTest"
provisioned-reads = 30
provisioned-writes = 20
}
membership {
table-name = "membershipTest"
provisioned-reads = 30
provisioned-writes = 20
} }
} }

View File

@ -23,13 +23,14 @@ import cats.implicits._
import fs2.{Scheduler, Stream} import fs2.{Scheduler, Stream}
import org.joda.time.DateTime import org.joda.time.DateTime
import org.scalatest.concurrent.Eventually import org.scalatest.concurrent.Eventually
import org.scalatest.mockito.MockitoSugar
import org.scalatest.time.{Millis, Seconds, Span} import org.scalatest.time.{Millis, Seconds, Span}
import vinyldns.api.{DynamoDBApiIntegrationSpec, VinylDNSTestData} import vinyldns.api.{DynamoDBApiIntegrationSpec, VinylDNSTestData}
import vinyldns.api.domain.record.RecordSetChangeGenerator import vinyldns.api.domain.record.RecordSetChangeGenerator
import vinyldns.core.domain.batch.BatchChangeRepository
import vinyldns.core.domain.record._ import vinyldns.core.domain.record._
import vinyldns.api.domain.zone._ import vinyldns.api.domain.zone._
import vinyldns.api.engine.sqs.SqsConnection import vinyldns.api.engine.sqs.SqsConnection
import vinyldns.api.repository.ApiDataAccessor
import vinyldns.dynamodb.repository.{ import vinyldns.dynamodb.repository.{
DynamoDBRecordChangeRepository, DynamoDBRecordChangeRepository,
DynamoDBRecordSetRepository, DynamoDBRecordSetRepository,
@ -37,14 +38,21 @@ import vinyldns.dynamodb.repository.{
DynamoDBZoneChangeRepository DynamoDBZoneChangeRepository
} }
import vinyldns.api.repository.mysql.TestMySqlInstance import vinyldns.api.repository.mysql.TestMySqlInstance
import vinyldns.core.domain.membership.{
GroupChangeRepository,
GroupRepository,
MembershipRepository,
UserRepository
}
import vinyldns.core.domain.zone._ import vinyldns.core.domain.zone._
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.ExecutionContext
class ZoneCommandHandlerIntegrationSpec class ZoneCommandHandlerIntegrationSpec
extends DynamoDBApiIntegrationSpec extends DynamoDBApiIntegrationSpec
with VinylDNSTestData with VinylDNSTestData
with MockitoSugar
with Eventually { with Eventually {
import vinyldns.api.engine.sqs.SqsConverters._ import vinyldns.api.engine.sqs.SqsConverters._
@ -65,11 +73,7 @@ class ZoneCommandHandlerIntegrationSpec
PatienceConfig(timeout = Span(5, Seconds), interval = Span(500, Millis)) PatienceConfig(timeout = Span(5, Seconds), interval = Span(500, Millis))
private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
private var recordChangeRepo: RecordChangeRepository = _ private var repositories: ApiDataAccessor = _
private var recordSetRepo: RecordSetRepository = _
private var zoneChangeRepo: ZoneChangeRepository = _
private var zoneRepo: ZoneRepository = _
private var batchChangeRepo: BatchChangeRepository = _
private var sqsConn: SqsConnection = _ private var sqsConn: SqsConnection = _
private var str: Stream[IO, Unit] = _ private var str: Stream[IO, Unit] = _
private val stopSignal = fs2.async.signalOf[IO, Boolean](false).unsafeRunSync() private val stopSignal = fs2.async.signalOf[IO, Boolean](false).unsafeRunSync()
@ -119,38 +123,36 @@ class ZoneCommandHandlerIntegrationSpec
} }
def setup(): Unit = { def setup(): Unit = {
val repos = ( val dynamoRepos = (
DynamoDBRecordChangeRepository(recordChangeStoreConfig, dynamoIntegrationConfig),
DynamoDBRecordSetRepository(recordSetStoreConfig, dynamoIntegrationConfig), DynamoDBRecordSetRepository(recordSetStoreConfig, dynamoIntegrationConfig),
DynamoDBRecordChangeRepository(recordChangeStoreConfig, dynamoIntegrationConfig),
DynamoDBZoneChangeRepository(zoneChangeStoreConfig, dynamoIntegrationConfig) DynamoDBZoneChangeRepository(zoneChangeStoreConfig, dynamoIntegrationConfig)
).parTupled.unsafeRunSync() ).parTupled.unsafeRunSync()
recordChangeRepo = repos._1 repositories = ApiDataAccessor(
recordSetRepo = repos._2 mock[UserRepository],
zoneChangeRepo = repos._3 mock[GroupRepository],
zoneRepo = TestMySqlInstance.zoneRepository mock[MembershipRepository],
batchChangeRepo = TestMySqlInstance.batchChangeRepository mock[GroupChangeRepository],
dynamoRepos._1,
dynamoRepos._2,
dynamoRepos._3,
TestMySqlInstance.zoneRepository,
TestMySqlInstance.batchChangeRepository
)
sqsConn = SqsConnection() sqsConn = SqsConnection()
//seed items database //seed items database
waitForSuccess(zoneRepo.save(testZone)) (
waitForSuccess(recordChangeRepo.save(inDbRecordChange)) repositories.zoneRepository.save(testZone),
waitForSuccess(recordChangeRepo.save(inDbRecordChangeForSyncTest)) repositories.recordChangeRepository.save(inDbRecordChange),
waitForSuccess(recordSetRepo.apply(inDbRecordChange)) repositories.recordChangeRepository.save(inDbRecordChangeForSyncTest),
waitForSuccess(recordSetRepo.apply(inDbRecordChangeForSyncTest)) repositories.recordSetRepository.apply(inDbRecordChange),
waitForSuccess(zoneChangeRepo.save(inDbZoneChange)) repositories.recordSetRepository.apply(inDbRecordChangeForSyncTest),
// Run a noop query to make sure recordSetRepo is up repositories.zoneChangeRepository.save(inDbZoneChange)).parTupled.unsafeRunSync()
waitForSuccess(recordSetRepo.listRecordSets("1", None, None, None))
str = ZoneCommandHandler.mainFlow( str = ZoneCommandHandler.mainFlow(repositories, sqsConn, 100.millis, stopSignal)
zoneRepo,
zoneChangeRepo,
recordSetRepo,
recordChangeRepo,
batchChangeRepo,
sqsConn,
100.millis,
stopSignal)
str.compile.drain.unsafeRunAsync { _ => str.compile.drain.unsafeRunAsync { _ =>
() ()
} }
@ -168,7 +170,7 @@ class ZoneCommandHandlerIntegrationSpec
sendCommand(change, sqsConn).unsafeRunSync() sendCommand(change, sqsConn).unsafeRunSync()
eventually { eventually {
val getZone = zoneRepo.getZone(testZone.id).unsafeToFuture() val getZone = repositories.zoneRepository.getZone(testZone.id).unsafeToFuture()
whenReady(getZone) { zn => whenReady(getZone) { zn =>
zn.get.email shouldBe "updated@test.com" zn.get.email shouldBe "updated@test.com"
} }
@ -180,7 +182,9 @@ class ZoneCommandHandlerIntegrationSpec
RecordSetChangeGenerator.forUpdate(inDbRecordSet, inDbRecordSet.copy(ttl = 1234), testZone) RecordSetChangeGenerator.forUpdate(inDbRecordSet, inDbRecordSet.copy(ttl = 1234), testZone)
sendCommand(change, sqsConn).unsafeRunSync() sendCommand(change, sqsConn).unsafeRunSync()
eventually { eventually {
val getRs = recordSetRepo.getRecordSet(testZone.id, inDbRecordSet.id).unsafeToFuture() val getRs = repositories.recordSetRepository
.getRecordSet(testZone.id, inDbRecordSet.id)
.unsafeToFuture()
whenReady(getRs) { rs => whenReady(getRs) { rs =>
rs.get.ttl shouldBe 1234 rs.get.ttl shouldBe 1234
} }
@ -192,8 +196,9 @@ class ZoneCommandHandlerIntegrationSpec
sendCommand(change, sqsConn).unsafeRunSync() sendCommand(change, sqsConn).unsafeRunSync()
eventually { eventually {
val validatingQueries = for { val validatingQueries = for {
rs <- recordSetRepo.getRecordSet(testZone.id, inDbRecordSetForSyncTest.id) rs <- repositories.recordSetRepository
ch <- recordChangeRepo.listRecordSetChanges(testZone.id) .getRecordSet(testZone.id, inDbRecordSetForSyncTest.id)
ch <- repositories.recordChangeRepository.listRecordSetChanges(testZone.id)
} yield (rs, ch) } yield (rs, ch)
whenReady(validatingQueries.unsafeToFuture()) { data => whenReady(validatingQueries.unsafeToFuture()) { data =>
@ -210,9 +215,4 @@ class ZoneCommandHandlerIntegrationSpec
} }
} }
} }
private def waitForSuccess[T](f: => IO[T]): T = {
val waiting = f.unsafeToFuture().recover { case _ => Thread.sleep(2000); waitForSuccess(f) }
Await.result[T](waiting, 15.seconds)
}
} }

View File

@ -20,11 +20,15 @@ import vinyldns.api.VinylDNSConfig
import vinyldns.api.crypto.Crypto import vinyldns.api.crypto.Crypto
import vinyldns.core.domain.batch.BatchChangeRepository import vinyldns.core.domain.batch.BatchChangeRepository
import vinyldns.core.domain.zone.{ZoneChangeRepository, ZoneRepository} import vinyldns.core.domain.zone.{ZoneChangeRepository, ZoneRepository}
import vinyldns.core.repository.{DataStore, RepositoryName} import vinyldns.core.repository.{DataStore, DataStoreConfig, RepositoryName}
object TestMySqlInstance { object TestMySqlInstance {
lazy val mySqlConfig: DataStoreConfig =
pureconfig.loadConfigOrThrow[DataStoreConfig](VinylDNSConfig.vinyldnsConfig, "mysql")
lazy val instance: DataStore = lazy val instance: DataStore =
new MySqlDataStoreProvider().load(VinylDNSConfig.mySqlConfig, Crypto.instance).unsafeRunSync() new MySqlDataStoreProvider().load(mySqlConfig, Crypto.instance).unsafeRunSync()
lazy val zoneRepository: ZoneRepository = lazy val zoneRepository: ZoneRepository =
instance.get[ZoneRepository](RepositoryName.zone).get instance.get[ZoneRepository](RepositoryName.zone).get

View File

@ -46,6 +46,44 @@ vinyldns {
} }
} }
dynamodb.repositories {
record-set {
table-name = "recordSetTest"
provisioned-reads = 30
provisioned-writes = 20
}
record-change {
table-name = "recordChangeTest"
provisioned-reads = 30
provisioned-writes = 20
}
zone-change {
table-name = "zoneChangesTest"
provisioned-reads = 30
provisioned-writes = 20
}
user {
table-name = "usersTest"
provisioned-reads = 30
provisioned-writes = 20
}
group {
table-name = "groupsTest"
provisioned-reads = 30
provisioned-writes = 20
}
group-change {
table-name = "groupChangesTest"
provisioned-reads = 30
provisioned-writes = 20
}
membership {
table-name = "membershipTest"
provisioned-reads = 30
provisioned-writes = 20
}
}
sync-delay = 10000 # 10 second delay for resyncing zone sync-delay = 10000 # 10 second delay for resyncing zone
batch-change-limit = 20 # Max change limit per batch request batch-change-limit = 20 # Max change limit per batch request

View File

@ -33,7 +33,7 @@ vinyldns {
port = 9000 port = 9000
} }
data-stores = ["mysql"] data-stores = ["mysql", "dynamodb"]
mysql { mysql {
class-name = "vinyldns.api.repository.mysql.MySqlDataStoreProvider" class-name = "vinyldns.api.repository.mysql.MySqlDataStoreProvider"
@ -59,60 +59,18 @@ vinyldns {
} }
} }
dynamo { dynamodb {
key = "vinyldnsTest" class-name = "vinyldns.dynamodb.repository.DynamoDBDataStoreProvider"
secret = "notNeededForDynamoDbLocal"
endpoint = "http://127.0.0.1:19000"
region = "us-east-1" # note: we are always in us-east-1, but this can be overridden
}
zoneChanges { settings {
dynamo { key = "vinyldnsTest"
tableName = "zoneChanges" secret = "notNeededForDynamoDbLocal"
provisionedReads=30 endpoint = "http://127.0.0.1:19000"
provisionedWrites=30 region = "us-east-1" # note: we are always in us-east-1, but this can be overridden
} }
}
recordSet { repositories {
dynamo { # override
tableName = "recordSet"
provisionedReads=30
provisionedWrites=30
}
}
recordChange {
dynamo {
tableName = "recordChange"
provisionedReads=30
provisionedWrites=30
}
}
users {
dynamo {
tableName = "users"
provisionedReads=30
provisionedWrites=30
}
}
groups {
dynamo {
tableName = "groups"
provisionedReads=30
provisionedWrites=30
}
}
groupChanges {
dynamo {
tableName = "groupChanges"
provisionedReads=30
provisionedWrites=30
}
}
membership {
dynamo {
tableName = "membership"
provisionedReads=30
provisionedWrites=30
} }
} }

View File

@ -32,14 +32,10 @@ import vinyldns.api.domain.record.RecordSetService
import vinyldns.api.domain.zone._ import vinyldns.api.domain.zone._
import vinyldns.api.engine.ProductionZoneCommandHandler import vinyldns.api.engine.ProductionZoneCommandHandler
import vinyldns.api.engine.sqs.{SqsCommandBus, SqsConnection} import vinyldns.api.engine.sqs.{SqsCommandBus, SqsConnection}
import vinyldns.dynamodb.repository._ import vinyldns.api.repository.{ApiDataAccessor, ApiDataAccessorProvider, TestDataLoader}
import vinyldns.api.repository.TestDataLoader
import vinyldns.api.repository.mysql.MySqlDataStoreProvider
import vinyldns.api.route.{HealthService, VinylDNSService} import vinyldns.api.route.{HealthService, VinylDNSService}
import vinyldns.core.VinylDNSMetrics import vinyldns.core.VinylDNSMetrics
import vinyldns.core.domain.batch.BatchChangeRepository import vinyldns.core.repository.DataStoreLoader
import vinyldns.core.domain.zone.ZoneRepository
import vinyldns.core.repository.{DataStoreStartupError, RepositoryName}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.io.{Codec, Source} import scala.io.{Codec, Source}
@ -65,41 +61,10 @@ object Boot extends App {
for { for {
banner <- vinyldnsBanner() banner <- vinyldnsBanner()
crypto <- IO(Crypto.instance) // load crypto crypto <- IO(Crypto.instance) // load crypto
// TODO datastore loading will not be hardcoded by type here repoConfigs <- VinylDNSConfig.dataStoreConfigs
mySqlDataStore <- new MySqlDataStoreProvider().load(VinylDNSConfig.mySqlConfig, crypto) repositories <- DataStoreLoader
zoneRepo <- IO.fromEither( .loadAll[ApiDataAccessor](repoConfigs, crypto, ApiDataAccessorProvider)
mySqlDataStore _ <- TestDataLoader.loadTestData(repositories.userRepository)
.get[ZoneRepository](RepositoryName.zone)
.toRight[Throwable](DataStoreStartupError("Missing zone repository")))
batchChangeRepo <- IO.fromEither(
mySqlDataStore
.get[BatchChangeRepository](RepositoryName.batchChange)
.toRight[Throwable](DataStoreStartupError("Missing zone repository")))
// TODO this also will all be removed with dynamic loading
userRepo <- DynamoDBUserRepository(
VinylDNSConfig.usersStoreConfig,
VinylDNSConfig.dynamoConfig,
crypto
)
groupRepo <- DynamoDBGroupRepository(
VinylDNSConfig.groupsStoreConfig,
VinylDNSConfig.dynamoConfig)
membershipRepo <- DynamoDBMembershipRepository(
VinylDNSConfig.membershipStoreConfig,
VinylDNSConfig.dynamoConfig)
groupChangeRepo <- DynamoDBGroupChangeRepository(
VinylDNSConfig.groupChangesStoreConfig,
VinylDNSConfig.dynamoConfig)
recordSetRepo <- DynamoDBRecordSetRepository(
VinylDNSConfig.recordSetStoreConfig,
VinylDNSConfig.dynamoConfig)
recordChangeRepo <- DynamoDBRecordChangeRepository(
VinylDNSConfig.recordChangeStoreConfig,
VinylDNSConfig.dynamoConfig)
zoneChangeRepo <- DynamoDBZoneChangeRepository(
VinylDNSConfig.zoneChangeStoreConfig,
VinylDNSConfig.dynamoConfig)
_ <- TestDataLoader.loadTestData(userRepo)
sqsConfig <- IO(VinylDNSConfig.sqsConfig) sqsConfig <- IO(VinylDNSConfig.sqsConfig)
sqsConnection <- IO(SqsConnection(sqsConfig)) sqsConnection <- IO(SqsConnection(sqsConfig))
processingDisabled <- IO(VinylDNSConfig.vinyldnsConfig.getBoolean("processing-disabled")) processingDisabled <- IO(VinylDNSConfig.vinyldnsConfig.getBoolean("processing-disabled"))
@ -109,47 +74,26 @@ object Boot extends App {
batchChangeLimit <- IO(VinylDNSConfig.vinyldnsConfig.getInt("batch-change-limit")) batchChangeLimit <- IO(VinylDNSConfig.vinyldnsConfig.getInt("batch-change-limit"))
syncDelay <- IO(VinylDNSConfig.vinyldnsConfig.getInt("sync-delay")) syncDelay <- IO(VinylDNSConfig.vinyldnsConfig.getInt("sync-delay"))
_ <- fs2.async.start( _ <- fs2.async.start(
ProductionZoneCommandHandler.run( ProductionZoneCommandHandler.run(sqsConnection, processingSignal, repositories, sqsConfig))
sqsConnection,
processingSignal,
zoneRepo,
zoneChangeRepo,
recordChangeRepo,
recordSetRepo,
batchChangeRepo,
sqsConfig))
} yield { } yield {
val zoneValidations = new ZoneValidations(syncDelay) val zoneValidations = new ZoneValidations(syncDelay)
val batchChangeValidations = new BatchChangeValidations(batchChangeLimit, AccessValidations) val batchChangeValidations = new BatchChangeValidations(batchChangeLimit, AccessValidations)
val commandBus = new SqsCommandBus(sqsConnection) val commandBus = new SqsCommandBus(sqsConnection)
val membershipService = val membershipService = MembershipService(repositories)
new MembershipService(groupRepo, userRepo, membershipRepo, zoneRepo, groupChangeRepo)
val connectionValidator = val connectionValidator =
new ZoneConnectionValidator(VinylDNSConfig.defaultZoneConnection) new ZoneConnectionValidator(VinylDNSConfig.defaultZoneConnection)
val recordSetService = new RecordSetService( val recordSetService = RecordSetService(repositories, commandBus, AccessValidations)
zoneRepo, val zoneService = ZoneService(
recordSetRepo, repositories,
recordChangeRepo,
userRepo,
commandBus,
AccessValidations)
val zoneService = new ZoneService(
zoneRepo,
groupRepo,
userRepo,
zoneChangeRepo,
connectionValidator, connectionValidator,
commandBus, commandBus,
zoneValidations, zoneValidations,
AccessValidations) AccessValidations)
val healthService = new HealthService(zoneRepo) val healthService = new HealthService(repositories.zoneRepository)
val batchChangeConverter = new BatchChangeConverter(batchChangeRepo, commandBus) val batchChangeConverter =
val batchChangeService = new BatchChangeService( new BatchChangeConverter(repositories.batchChangeRepository, commandBus)
zoneRepo, val batchChangeService =
recordSetRepo, BatchChangeService(repositories, batchChangeValidations, batchChangeConverter)
batchChangeValidations,
batchChangeRepo,
batchChangeConverter)
val collectorRegistry = CollectorRegistry.defaultRegistry val collectorRegistry = CollectorRegistry.defaultRegistry
val vinyldnsService = new VinylDNSService( val vinyldnsService = new VinylDNSService(
membershipService, membershipService,
@ -158,7 +102,10 @@ object Boot extends App {
healthService, healthService,
recordSetService, recordSetService,
batchChangeService, batchChangeService,
collectorRegistry) collectorRegistry,
repositories.userRepository,
repositories.membershipRepository
)
DefaultExports.initialize() DefaultExports.initialize()
collectorRegistry.register(new DropwizardExports(VinylDNSMetrics.metricsRegistry)) collectorRegistry.register(new DropwizardExports(VinylDNSMetrics.metricsRegistry))

View File

@ -17,7 +17,10 @@
package vinyldns.api package vinyldns.api
import akka.actor.ActorSystem import akka.actor.ActorSystem
import cats.effect.IO
import cats.implicits._
import com.typesafe.config.{Config, ConfigFactory} import com.typesafe.config.{Config, ConfigFactory}
import pureconfig.module.catseffect.loadConfigF
import pureconfig.{CamelCase, ConfigFieldMapping, ProductHint} import pureconfig.{CamelCase, ConfigFieldMapping, ProductHint}
import vinyldns.api.VinylDNSConfig.vinyldnsConfig import vinyldns.api.VinylDNSConfig.vinyldnsConfig
import vinyldns.api.crypto.Crypto import vinyldns.api.crypto.Crypto
@ -33,21 +36,18 @@ object VinylDNSConfig {
lazy val config: Config = ConfigFactory.load() lazy val config: Config = ConfigFactory.load()
lazy val vinyldnsConfig: Config = config.getConfig("vinyldns") lazy val vinyldnsConfig: Config = config.getConfig("vinyldns")
lazy val dynamoConfig = DynamoConfig.dynamoConfig lazy val dataStoreConfigs: IO[List[DataStoreConfig]] =
lazy val zoneChangeStoreConfig: DynamoDBRepositorySettings = DynamoConfig.zoneChangeStoreConfig vinyldnsConfig
lazy val recordSetStoreConfig: DynamoDBRepositorySettings = DynamoConfig.recordSetStoreConfig .getStringList("data-stores")
lazy val recordChangeStoreConfig: DynamoDBRepositorySettings = .asScala
DynamoConfig.recordChangeStoreConfig .toList
lazy val usersStoreConfig: DynamoDBRepositorySettings = DynamoConfig.usersStoreConfig .map { configKey =>
lazy val groupsStoreConfig: DynamoDBRepositorySettings = DynamoConfig.groupsStoreConfig loadConfigF[IO, DataStoreConfig](vinyldnsConfig, configKey)
lazy val groupChangesStoreConfig: DynamoDBRepositorySettings = }
DynamoConfig.groupChangesStoreConfig .parSequence
lazy val membershipStoreConfig: DynamoDBRepositorySettings = DynamoConfig.membershipStoreConfig
lazy val restConfig: Config = vinyldnsConfig.getConfig("rest") lazy val restConfig: Config = vinyldnsConfig.getConfig("rest")
lazy val monitoringConfig: Config = vinyldnsConfig.getConfig("monitoring") lazy val monitoringConfig: Config = vinyldnsConfig.getConfig("monitoring")
lazy val mySqlConfig: DataStoreConfig =
pureconfig.loadConfigOrThrow[DataStoreConfig](vinyldnsConfig, "mysql")
lazy val sqsConfig: Config = vinyldnsConfig.getConfig("sqs") lazy val sqsConfig: Config = vinyldnsConfig.getConfig("sqs")
lazy val cryptoConfig: Config = vinyldnsConfig.getConfig("crypto") lazy val cryptoConfig: Config = vinyldnsConfig.getConfig("crypto")
lazy val system: ActorSystem = ActorSystem("VinylDNS", VinylDNSConfig.config) lazy val system: ActorSystem = ActorSystem("VinylDNS", VinylDNSConfig.config)

View File

@ -17,10 +17,7 @@
package vinyldns.api.domain.auth package vinyldns.api.domain.auth
import cats.effect._ import cats.effect._
import vinyldns.api.VinylDNSConfig
import vinyldns.api.crypto.Crypto
import vinyldns.core.domain.membership.{MembershipRepository, User, UserRepository} import vinyldns.core.domain.membership.{MembershipRepository, User, UserRepository}
import vinyldns.dynamodb.repository.{DynamoDBMembershipRepository, DynamoDBUserRepository}
import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.core.route.Monitored import vinyldns.core.route.Monitored
@ -52,19 +49,3 @@ class MembershipAuthPrincipalProvider(
membershipRepo.getGroupsForUser(userId) membershipRepo.getGroupsForUser(userId)
} }
} }
object MembershipAuthPrincipalProvider {
// TODO this has to be dynamic!!!
val userRepository: UserRepository =
DynamoDBUserRepository(
VinylDNSConfig.usersStoreConfig,
VinylDNSConfig.dynamoConfig,
Crypto.instance)
.unsafeRunSync()
val membershipRepository: MembershipRepository =
DynamoDBMembershipRepository(VinylDNSConfig.membershipStoreConfig, VinylDNSConfig.dynamoConfig)
.unsafeRunSync()
def apply(): MembershipAuthPrincipalProvider =
new MembershipAuthPrincipalProvider(userRepository, membershipRepository)
}

View File

@ -29,8 +29,22 @@ import vinyldns.core.domain.record.RecordType._
import vinyldns.core.domain.record.{RecordSet, RecordSetRepository} import vinyldns.core.domain.record.{RecordSet, RecordSetRepository}
import vinyldns.core.domain.zone.ZoneRepository import vinyldns.core.domain.zone.ZoneRepository
import vinyldns.api.domain.{RecordAlreadyExists, ZoneDiscoveryError} import vinyldns.api.domain.{RecordAlreadyExists, ZoneDiscoveryError}
import vinyldns.api.repository.ApiDataAccessor
import vinyldns.core.domain.batch.{BatchChange, BatchChangeRepository, BatchChangeSummaryList} import vinyldns.core.domain.batch.{BatchChange, BatchChangeRepository, BatchChangeSummaryList}
object BatchChangeService {
def apply(
dataAccessor: ApiDataAccessor,
batchChangeValidations: BatchChangeValidationsAlgebra,
batchChangeConverter: BatchChangeConverterAlgebra): BatchChangeService =
new BatchChangeService(
dataAccessor.zoneRepository,
dataAccessor.recordSetRepository,
batchChangeValidations,
dataAccessor.batchChangeRepository,
batchChangeConverter)
}
class BatchChangeService( class BatchChangeService(
zoneRepository: ZoneRepository, zoneRepository: ZoneRepository,
recordSetRepository: RecordSetRepository, recordSetRepository: RecordSetRepository,

View File

@ -18,11 +18,23 @@ package vinyldns.api.domain.membership
import cats.implicits._ import cats.implicits._
import vinyldns.api.Interfaces._ import vinyldns.api.Interfaces._
import vinyldns.api.repository.ApiDataAccessor
import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.core.domain.membership.LockStatus.LockStatus import vinyldns.core.domain.membership.LockStatus.LockStatus
import vinyldns.core.domain.zone.ZoneRepository import vinyldns.core.domain.zone.ZoneRepository
import vinyldns.core.domain.membership._ import vinyldns.core.domain.membership._
object MembershipService {
def apply(dataAccessor: ApiDataAccessor): MembershipService =
new MembershipService(
dataAccessor.groupRepository,
dataAccessor.userRepository,
dataAccessor.membershipRepository,
dataAccessor.zoneRepository,
dataAccessor.groupChangeRepository
)
}
class MembershipService( class MembershipService(
groupRepo: GroupRepository, groupRepo: GroupRepository,
userRepo: UserRepository, userRepo: UserRepository,

View File

@ -22,10 +22,26 @@ import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.api.domain.engine.EngineCommandBus import vinyldns.api.domain.engine.EngineCommandBus
import vinyldns.core.domain.membership.{User, UserRepository} import vinyldns.core.domain.membership.{User, UserRepository}
import vinyldns.api.domain.zone._ import vinyldns.api.domain.zone._
import vinyldns.api.repository.ApiDataAccessor
import vinyldns.api.route.ListRecordSetsResponse import vinyldns.api.route.ListRecordSetsResponse
import vinyldns.core.domain.record._ import vinyldns.core.domain.record._
import vinyldns.core.domain.zone.{Zone, ZoneCommandResult, ZoneRepository} import vinyldns.core.domain.zone.{Zone, ZoneCommandResult, ZoneRepository}
object RecordSetService {
def apply(
dataAccessor: ApiDataAccessor,
commandBus: EngineCommandBus,
accessValidation: AccessValidationAlgebra): RecordSetService =
new RecordSetService(
dataAccessor.zoneRepository,
dataAccessor.recordSetRepository,
dataAccessor.recordChangeRepository,
dataAccessor.userRepository,
commandBus,
accessValidation
)
}
class RecordSetService( class RecordSetService(
zoneRepository: ZoneRepository, zoneRepository: ZoneRepository,
recordSetRepository: RecordSetRepository, recordSetRepository: RecordSetRepository,

View File

@ -21,9 +21,29 @@ import vinyldns.api.Interfaces._
import vinyldns.api.domain.AccessValidationAlgebra import vinyldns.api.domain.AccessValidationAlgebra
import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.api.domain.engine.EngineCommandBus import vinyldns.api.domain.engine.EngineCommandBus
import vinyldns.api.repository.ApiDataAccessor
import vinyldns.core.domain.membership.{Group, GroupRepository, User, UserRepository} import vinyldns.core.domain.membership.{Group, GroupRepository, User, UserRepository}
import vinyldns.core.domain.zone._ import vinyldns.core.domain.zone._
object ZoneService {
def apply(
dataAccessor: ApiDataAccessor,
connectionValidator: ZoneConnectionValidatorAlgebra,
commandBus: EngineCommandBus,
zoneValidations: ZoneValidations,
accessValidation: AccessValidationAlgebra): ZoneService =
new ZoneService(
dataAccessor.zoneRepository,
dataAccessor.groupRepository,
dataAccessor.userRepository,
dataAccessor.zoneChangeRepository,
connectionValidator,
commandBus,
zoneValidations,
accessValidation
)
}
class ZoneService( class ZoneService(
zoneRepository: ZoneRepository, zoneRepository: ZoneRepository,
groupRepository: GroupRepository, groupRepository: GroupRepository,

View File

@ -26,10 +26,10 @@ import fs2.async.mutable.Signal
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import vinyldns.api.VinylDNSConfig import vinyldns.api.VinylDNSConfig
import vinyldns.api.domain.dns.DnsConnection import vinyldns.api.domain.dns.DnsConnection
import vinyldns.core.domain.record.{RecordChangeRepository, RecordSetChange, RecordSetRepository} import vinyldns.core.domain.record.RecordSetChange
import vinyldns.core.domain.zone.{ZoneChange, ZoneChangeRepository, ZoneChangeType, ZoneRepository} import vinyldns.core.domain.zone.{ZoneChange, ZoneChangeType}
import vinyldns.api.engine.sqs.SqsConnection import vinyldns.api.engine.sqs.SqsConnection
import vinyldns.core.domain.batch.BatchChangeRepository import vinyldns.api.repository.ApiDataAccessor
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -63,11 +63,7 @@ object ZoneCommandHandler {
extends ChangeRequest extends ChangeRequest
def mainFlow( def mainFlow(
zoneRepository: ZoneRepository, dataAccessor: ApiDataAccessor,
zoneChangeRepository: ZoneChangeRepository,
recordSetRepository: RecordSetRepository,
recordChangeRepository: RecordChangeRepository,
batchChangeRepository: BatchChangeRepository,
sqsConnection: SqsConnection, sqsConnection: SqsConnection,
pollingInterval: FiniteDuration, pollingInterval: FiniteDuration,
pauseSignal: Signal[IO, Boolean])(implicit scheduler: Scheduler): Stream[IO, Unit] = { pauseSignal: Signal[IO, Boolean])(implicit scheduler: Scheduler): Stream[IO, Unit] = {
@ -79,10 +75,15 @@ object ZoneCommandHandler {
val increaseTimeoutForZoneSyncs = changeVisibilityTimeoutForZoneSyncs(sqsConnection) val increaseTimeoutForZoneSyncs = changeVisibilityTimeoutForZoneSyncs(sqsConnection)
// Handlers for each type of change request // Handlers for each type of change request
val zoneChangeHandler = ZoneChangeHandler(zoneRepository, zoneChangeRepository) val zoneChangeHandler =
ZoneChangeHandler(dataAccessor.zoneRepository, dataAccessor.zoneChangeRepository)
val recordChangeHandler = val recordChangeHandler =
RecordSetChangeHandler(recordSetRepository, recordChangeRepository, batchChangeRepository) RecordSetChangeHandler(
val zoneSyncHandler = ZoneSyncHandler(recordSetRepository, recordChangeRepository) dataAccessor.recordSetRepository,
dataAccessor.recordChangeRepository,
dataAccessor.batchChangeRepository)
val zoneSyncHandler =
ZoneSyncHandler(dataAccessor.recordSetRepository, dataAccessor.recordChangeRepository)
val changeRequestProcessor = val changeRequestProcessor =
processChangeRequests(zoneChangeHandler, recordChangeHandler, zoneSyncHandler) processChangeRequests(zoneChangeHandler, recordChangeHandler, zoneSyncHandler)
@ -237,11 +238,7 @@ object ProductionZoneCommandHandler {
def run( def run(
sqsConnection: SqsConnection, sqsConnection: SqsConnection,
processingSignal: Signal[IO, Boolean], processingSignal: Signal[IO, Boolean],
zoneRepository: ZoneRepository, dataAccessor: ApiDataAccessor,
zoneChangeRepository: ZoneChangeRepository,
recordChangeRepository: RecordChangeRepository,
recordSetRepository: RecordSetRepository,
batchChangeRepository: BatchChangeRepository,
config: Config): IO[Unit] = { config: Config): IO[Unit] = {
implicit val scheduler: Scheduler = implicit val scheduler: Scheduler =
Scheduler.fromScheduledExecutorService(Executors.newScheduledThreadPool(2)) Scheduler.fromScheduledExecutorService(Executors.newScheduledThreadPool(2))
@ -250,15 +247,7 @@ object ProductionZoneCommandHandler {
pollingInterval <- IO.pure( pollingInterval <- IO.pure(
config.getDuration("polling-interval", TimeUnit.MILLISECONDS).milliseconds) config.getDuration("polling-interval", TimeUnit.MILLISECONDS).milliseconds)
flow <- ZoneCommandHandler flow <- ZoneCommandHandler
.mainFlow( .mainFlow(dataAccessor, sqsConnection, pollingInterval, processingSignal)
zoneRepository,
zoneChangeRepository,
recordSetRepository,
recordChangeRepository,
batchChangeRepository,
sqsConnection,
pollingInterval,
processingSignal)
.compile .compile
.drain .drain
} yield flow } yield flow

View File

@ -21,7 +21,7 @@ import akka.http.scaladsl.server.RequestContext
import cats.effect._ import cats.effect._
import cats.syntax.all._ import cats.syntax.all._
import vinyldns.api.crypto.Crypto import vinyldns.api.crypto.Crypto
import vinyldns.api.domain.auth.{AuthPrincipalProvider, MembershipAuthPrincipalProvider} import vinyldns.api.domain.auth.AuthPrincipalProvider
import vinyldns.core.crypto.CryptoAlgebra import vinyldns.core.crypto.CryptoAlgebra
import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.core.domain.membership.LockStatus import vinyldns.core.domain.membership.LockStatus
@ -34,9 +34,27 @@ final case class AuthMissing(msg: String) extends VinylDNSAuthenticationError(ms
final case class AuthRejected(reason: String) extends VinylDNSAuthenticationError(reason) final case class AuthRejected(reason: String) extends VinylDNSAuthenticationError(reason)
final case class AccountLocked(reason: String) extends VinylDNSAuthenticationError(reason) final case class AccountLocked(reason: String) extends VinylDNSAuthenticationError(reason)
trait VinylDNSAuthentication extends Monitored { trait VinylDNSAuthenticator {
val authenticator: Aws4Authenticator def authenticate(
val authPrincipalProvider: AuthPrincipalProvider ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]]
}
class ProductionVinylDNSAuthenticator(
val authenticator: Aws4Authenticator,
val authPrincipalProvider: AuthPrincipalProvider)
extends VinylDNSAuthenticator
with Monitored {
def authenticate(
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
// Need to refactor getAuthPrincipal to be an IO[Either[E, A]] instead of how it is implemented.
getAuthPrincipal(ctx, content).attempt.flatMap {
case Left(e: VinylDNSAuthenticationError) => IO.pure(Left(e))
case Right(ok) => IO.pure(Right(ok))
case Left(e) => IO.raiseError(e)
}
/** /**
* Gets the auth header from the request. If the auth header is not found then the * Gets the auth header from the request. If the auth header is not found then the
@ -111,7 +129,7 @@ trait VinylDNSAuthentication extends Monitored {
* @param ctx The Http Request Context * @param ctx The Http Request Context
* @return A Future containing the AuthPrincipal for the request. * @return A Future containing the AuthPrincipal for the request.
*/ */
def authenticate(ctx: RequestContext, content: String): IO[AuthPrincipal] = def getAuthPrincipal(ctx: RequestContext, content: String): IO[AuthPrincipal] =
for { for {
authHeader <- getAuthHeader(ctx) authHeader <- getAuthHeader(ctx)
regexMatch <- parseAuthHeader(authHeader) regexMatch <- parseAuthHeader(authHeader)
@ -138,30 +156,3 @@ trait VinylDNSAuthentication extends Monitored {
IO.raiseError(AuthRejected(s"Account with accessKey $accessKey specified was not found")) IO.raiseError(AuthRejected(s"Account with accessKey $accessKey specified was not found"))
} }
} }
class VinylDNSAuthenticator(
val authenticator: Aws4Authenticator,
val authPrincipalProvider: AuthPrincipalProvider)
extends VinylDNSAuthentication {
def apply(
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
// Need to refactor authenticate to be an IO[Either[E, A]] instead of how it is implemented, for the time being...
authenticate(ctx, content).attempt.flatMap {
case Left(e: VinylDNSAuthenticationError) => IO.pure(Left(e))
case Right(ok) => IO.pure(Right(ok))
case Left(e) => IO.raiseError(e)
}
}
object VinylDNSAuthenticator {
lazy val aws4Authenticator = new Aws4Authenticator
lazy val authPrincipalProvider = MembershipAuthPrincipalProvider()
lazy val authenticator = new VinylDNSAuthenticator(aws4Authenticator, authPrincipalProvider)
def apply(
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
authenticator.apply(ctx, content)
}

View File

@ -22,7 +22,6 @@ import akka.http.scaladsl.server._
import akka.http.scaladsl.server.directives.BasicDirectives import akka.http.scaladsl.server.directives.BasicDirectives
import cats.data.Validated.{Invalid, Valid} import cats.data.Validated.{Invalid, Valid}
import cats.data.ValidatedNel import cats.data.ValidatedNel
import cats.effect._
import org.json4s.JsonDSL._ import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._ import org.json4s.jackson.JsonMethods._
import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.auth.AuthPrincipal
@ -34,21 +33,14 @@ import scala.util.control.NonFatal
trait VinylDNSDirectives extends Directives { trait VinylDNSDirectives extends Directives {
/** val vinylDNSAuthenticator: VinylDNSAuthenticator
* Authenticator that takes a request context and yields an Authentication, which is an Either
* that holds a Left - Rejection, or Right - AuthPrincipal.
* @return an Authentication with the AuthPrincipal as looked up from the request, or a Left(Rejection)
*/
def vinyldnsAuthenticator(
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
VinylDNSAuthenticator(ctx, content)
def authenticate: Directive1[AuthPrincipal] = def authenticate: Directive1[AuthPrincipal] =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
extractRequestContext.flatMap { ctx => extractRequestContext.flatMap { ctx =>
extractStrictEntity(10.seconds).flatMap { strictEntity => extractStrictEntity(10.seconds).flatMap { strictEntity =>
onSuccess(vinyldnsAuthenticator(ctx, strictEntity.data.utf8String).unsafeToFuture()) onSuccess(
vinylDNSAuthenticator.authenticate(ctx, strictEntity.data.utf8String).unsafeToFuture())
.flatMap { .flatMap {
case Right(authPrincipal) case Right(authPrincipal)
provide(authPrincipal) provide(authPrincipal)

View File

@ -25,10 +25,12 @@ import akka.http.scaladsl.server.directives.LogEntry
import cats.effect.IO import cats.effect.IO
import fs2.async.mutable.Signal import fs2.async.mutable.Signal
import io.prometheus.client.CollectorRegistry import io.prometheus.client.CollectorRegistry
import vinyldns.api.domain.auth.MembershipAuthPrincipalProvider
import vinyldns.api.domain.batch.BatchChangeServiceAlgebra import vinyldns.api.domain.batch.BatchChangeServiceAlgebra
import vinyldns.api.domain.membership.MembershipServiceAlgebra import vinyldns.api.domain.membership.MembershipServiceAlgebra
import vinyldns.api.domain.record.RecordSetServiceAlgebra import vinyldns.api.domain.record.RecordSetServiceAlgebra
import vinyldns.api.domain.zone.ZoneServiceAlgebra import vinyldns.api.domain.zone.ZoneServiceAlgebra
import vinyldns.core.domain.membership.{MembershipRepository, UserRepository}
import scala.util.matching.Regex import scala.util.matching.Regex
@ -103,7 +105,9 @@ class VinylDNSService(
val healthService: HealthService, val healthService: HealthService,
val recordSetService: RecordSetServiceAlgebra, val recordSetService: RecordSetServiceAlgebra,
val batchChangeService: BatchChangeServiceAlgebra, val batchChangeService: BatchChangeServiceAlgebra,
val collectorRegistry: CollectorRegistry) val collectorRegistry: CollectorRegistry,
userRepository: UserRepository,
membershipRepository: MembershipRepository)
extends VinylDNSDirectives extends VinylDNSDirectives
with PingRoute with PingRoute
with ZoneRoute with ZoneRoute
@ -117,6 +121,12 @@ class VinylDNSService(
with VinylDNSJsonProtocol with VinylDNSJsonProtocol
with JsonValidationRejection { with JsonValidationRejection {
val aws4Authenticator = new Aws4Authenticator
val authPrincipalProvider =
new MembershipAuthPrincipalProvider(userRepository, membershipRepository)
val vinylDNSAuthenticator: VinylDNSAuthenticator =
new ProductionVinylDNSAuthenticator(aws4Authenticator, authPrincipalProvider)
// Authenticated routes must go first // Authenticated routes must go first
def authenticatedRoutes: server.Route = def authenticatedRoutes: server.Route =
handleRejections(validationRejectionHandler)(authenticate { authPrincipal => handleRejections(validationRejectionHandler)(authenticate { authPrincipal =>

View File

@ -30,67 +30,41 @@ vinyldns {
} }
} }
accounts { dynamodb.repositories {
dummy = true record-set {
table-name = "recordSetTest"
dynamo { provisioned-reads = 30
key = "dynamoKey" provisioned-writes = 20
secret = "dynamoSecret"
endpoint = "dynamoEndpoint"
} }
} record-change {
table-name = "recordChangeTest"
dynamo { provisioned-reads = 30
key="dynamoKey" provisioned-writes = 20
secret="dynamoSecret"
endpoint="dynamoEndpoint"
}
zoneChanges {
dynamo {
tableName = "zoneChanges"
provisionedReads=40
provisionedWrites=30
} }
} zone-change {
table-name = "zoneChangesTest"
recordSet { provisioned-reads = 30
dynamo { provisioned-writes = 20
tableName = "recordSet"
provisionedReads=40
provisionedWrites=30
} }
} user {
table-name = "usersTest"
recordChange { provisioned-reads = 30
dynamo { provisioned-writes = 20
tableName = "recordChange"
provisionedReads=40
provisionedWrites=30
} }
} group {
table-name = "groupsTest"
groups { provisioned-reads = 30
dynamo { provisioned-writes = 20
tableName = "groups"
provisionedReads=40
provisionedWrites=30
} }
} group-change {
table-name = "groupChangesTest"
groupChanges { provisioned-reads = 30
dynamo { provisioned-writes = 20
tableName = "groupChanges"
provisionedReads=40
provisionedWrites=30
} }
} membership {
table-name = "membershipTest"
membership { provisioned-reads = 30
dynamo { provisioned-writes = 20
tableName = "membership"
provisionedReads=40
provisionedWrites=30
} }
} }

View File

@ -17,6 +17,7 @@
package vinyldns.api package vinyldns.api
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import vinyldns.core.repository.RepositoryName._
class VinylDNSConfigSpec extends WordSpec with Matchers { class VinylDNSConfigSpec extends WordSpec with Matchers {
@ -26,46 +27,26 @@ class VinylDNSConfigSpec extends WordSpec with Matchers {
restConfig.getInt("port") shouldBe 9000 restConfig.getInt("port") shouldBe 9000
} }
"load the dynamo config" in { "properly load the datastore configs" in {
val dynamoConfig = VinylDNSConfig.dynamoConfig
dynamoConfig.key shouldBe "dynamoKey"
dynamoConfig.secret shouldBe "dynamoSecret"
dynamoConfig.endpoint shouldBe "dynamoEndpoint"
}
"load the zone change repository config" in { VinylDNSConfig.dataStoreConfigs.unsafeRunSync.length shouldBe 2
val config = VinylDNSConfig.zoneChangeStoreConfig
config.tableName shouldBe "zoneChanges"
config.provisionedReads shouldBe 40
config.provisionedWrites shouldBe 30
} }
"assign the correct mysql repositories" in {
val mysqlConfig =
VinylDNSConfig.dataStoreConfigs.unsafeRunSync
.find(_.className == "vinyldns.api.repository.mysql.MySqlDataStoreProvider")
.get
"load the record change repository config" in { mysqlConfig.repositories.keys should contain theSameElementsAs Set(zone, batchChange)
val config = VinylDNSConfig.recordChangeStoreConfig
config.tableName shouldBe "recordChange"
config.provisionedReads shouldBe 40
config.provisionedWrites shouldBe 30
} }
"assign the correct dynamodb repositories" in {
val dynamodbConfig =
VinylDNSConfig.dataStoreConfigs.unsafeRunSync
.find(_.className == "vinyldns.dynamodb.repository.DynamoDBDataStoreProvider")
.get
"load the membership repository config" in { dynamodbConfig.repositories.keys should contain theSameElementsAs
val config = VinylDNSConfig.membershipStoreConfig Set(user, group, membership, groupChange, recordSet, recordChange, zoneChange)
config.tableName shouldBe "membership"
config.provisionedReads shouldBe 40
config.provisionedWrites shouldBe 30
}
"load the record set repository config" in {
val config = VinylDNSConfig.recordSetStoreConfig
config.tableName shouldBe "recordSet"
config.provisionedReads shouldBe 40
config.provisionedWrites shouldBe 30
}
"load the group repository config" in {
val config = VinylDNSConfig.groupsStoreConfig
config.tableName shouldBe "groups"
config.provisionedReads shouldBe 40
config.provisionedWrites shouldBe 30
} }
} }
} }

View File

@ -47,6 +47,7 @@ class BatchChangeRoutingSpec
with GroupTestData { with GroupTestData {
val batchChangeService: BatchChangeServiceAlgebra = TestBatchChangeService val batchChangeService: BatchChangeServiceAlgebra = TestBatchChangeService
val vinylDNSAuthenticator: VinylDNSAuthenticator = new TestVinylDNSAuthenticator(okUserAuth)
import vinyldns.core.domain.batch.SingleChangeStatus._ import vinyldns.core.domain.batch.SingleChangeStatus._

View File

@ -25,7 +25,6 @@ import org.mockito.Mockito.doReturn
import org.scalatest.mockito.MockitoSugar import org.scalatest.mockito.MockitoSugar
import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} import org.scalatest.{Matchers, OneInstancePerTest, WordSpec}
import vinyldns.core.domain.zone.ZoneRepository import vinyldns.core.domain.zone.ZoneRepository
import cats.effect._ import cats.effect._
class HealthCheckRoutingSpec class HealthCheckRoutingSpec
@ -34,7 +33,6 @@ class HealthCheckRoutingSpec
with Directives with Directives
with HealthCheckRoute with HealthCheckRoute
with VinylDNSJsonProtocol with VinylDNSJsonProtocol
with VinylDNSDirectives
with OneInstancePerTest with OneInstancePerTest
with Matchers with Matchers
with MockitoSugar { with MockitoSugar {

View File

@ -51,6 +51,7 @@ class MembershipRoutingSpec
with BeforeAndAfterEach { with BeforeAndAfterEach {
val membershipService: MembershipService = mock[MembershipService] val membershipService: MembershipService = mock[MembershipService]
val vinylDNSAuthenticator: VinylDNSAuthenticator = new TestVinylDNSAuthenticator(okAuth)
override protected def beforeEach(): Unit = reset(membershipService) override protected def beforeEach(): Unit = reset(membershipService)

View File

@ -17,9 +17,8 @@
package vinyldns.api.route package vinyldns.api.route
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpRequest, StatusCodes} import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpRequest, StatusCodes}
import akka.http.scaladsl.server.{Directives, RequestContext, Route} import akka.http.scaladsl.server.{Directives, Route}
import akka.http.scaladsl.testkit.ScalatestRouteTest import akka.http.scaladsl.testkit.ScalatestRouteTest
import cats.effect._
import org.joda.time.DateTime import org.joda.time.DateTime
import org.json4s.JsonDSL._ import org.json4s.JsonDSL._
import org.json4s._ import org.json4s._
@ -482,10 +481,7 @@ class RecordSetRoutingSpec
val recordSetService: RecordSetServiceAlgebra = new TestService val recordSetService: RecordSetServiceAlgebra = new TestService
override def vinyldnsAuthenticator( val vinylDNSAuthenticator = new TestVinylDNSAuthenticator(okAuth)
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
IO.pure(Right(okAuth))
private def rsJson(recordSet: RecordSet): String = private def rsJson(recordSet: RecordSet): String =
compact(render(Extraction.decompose(recordSet))) compact(render(Extraction.decompose(recordSet)))

View File

@ -0,0 +1,29 @@
/*
* 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.route
import akka.http.scaladsl.server.RequestContext
import cats.effect.IO
import vinyldns.core.domain.auth.AuthPrincipal
class TestVinylDNSAuthenticator(authPrincipal: AuthPrincipal) extends VinylDNSAuthenticator {
def authenticate(
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
IO.pure(Right(authPrincipal))
}

View File

@ -24,7 +24,7 @@ import org.mockito.Mockito._
import org.scalatest.mockito.MockitoSugar import org.scalatest.mockito.MockitoSugar
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import vinyldns.api.domain.auth.AuthPrincipalProvider import vinyldns.api.domain.auth.AuthPrincipalProvider
import vinyldns.api.{GroupTestData} import vinyldns.api.GroupTestData
import vinyldns.core.crypto.CryptoAlgebra import vinyldns.core.crypto.CryptoAlgebra
class VinylDNSAuthenticatorSpec class VinylDNSAuthenticatorSpec
@ -35,7 +35,8 @@ class VinylDNSAuthenticatorSpec
private val mockAuthenticator = mock[Aws4Authenticator] private val mockAuthenticator = mock[Aws4Authenticator]
private val mockAuthPrincipalProvider = mock[AuthPrincipalProvider] private val mockAuthPrincipalProvider = mock[AuthPrincipalProvider]
private val underTest = new VinylDNSAuthenticator(mockAuthenticator, mockAuthPrincipalProvider) private val underTest =
new ProductionVinylDNSAuthenticator(mockAuthenticator, mockAuthPrincipalProvider)
"VinylDNSAuthenticator" should { "VinylDNSAuthenticator" should {
"use Crypto" in { "use Crypto" in {
@ -74,7 +75,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthenticator) .when(mockAuthenticator)
.authenticateReq(any[HttpRequest], any[List[String]], any[String], any[String]) .authenticateReq(any[HttpRequest], any[List[String]], any[String], any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Right(okUserAuth) result shouldBe Right(okUserAuth)
} }
"fail if missing Authorization header" in { "fail if missing Authorization header" in {
@ -95,7 +96,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthenticator) .when(mockAuthenticator)
.authenticateReq(any[HttpRequest], any[List[String]], any[String], any[String]) .authenticateReq(any[HttpRequest], any[List[String]], any[String], any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AuthMissing("Authorization header not found")) result shouldBe Left(AuthMissing("Authorization header not found"))
} }
"fail if Authorization header can not be parsed" in { "fail if Authorization header can not be parsed" in {
@ -110,7 +111,7 @@ class VinylDNSAuthenticatorSpec
val context: RequestContext = mock[RequestContext] val context: RequestContext = mock[RequestContext]
doReturn(httpRequest).when(context).request doReturn(httpRequest).when(context).request
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AuthRejected("Authorization header could not be parsed")) result shouldBe Left(AuthRejected("Authorization header could not be parsed"))
} }
"fail if the access key is missing" in { "fail if the access key is missing" in {
@ -133,7 +134,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthenticator) .when(mockAuthenticator)
.extractAccessKey(any[String]) .extractAccessKey(any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AuthMissing("accessKey not found")) result shouldBe Left(AuthMissing("accessKey not found"))
} }
"fail if the access key can not be retrieved" in { "fail if the access key can not be retrieved" in {
@ -156,7 +157,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthenticator) .when(mockAuthenticator)
.extractAccessKey(any[String]) .extractAccessKey(any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AuthRejected("Invalid authorization header")) result shouldBe Left(AuthRejected("Invalid authorization header"))
} }
"fail if the user is locked" in { "fail if the user is locked" in {
@ -182,7 +183,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthPrincipalProvider) .when(mockAuthPrincipalProvider)
.getAuthPrincipal(any[String]) .getAuthPrincipal(any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AccountLocked("Account with username locked is locked")) result shouldBe Left(AccountLocked("Account with username locked is locked"))
} }
"fail if the user can not be found" in { "fail if the user can not be found" in {
@ -209,7 +210,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthPrincipalProvider) .when(mockAuthPrincipalProvider)
.getAuthPrincipal(any[String]) .getAuthPrincipal(any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AuthRejected("Account with accessKey fakeKey specified was not found")) result shouldBe Left(AuthRejected("Account with accessKey fakeKey specified was not found"))
} }
"fail if signatures can not be validated" in { "fail if signatures can not be validated" in {
@ -240,7 +241,7 @@ class VinylDNSAuthenticatorSpec
.when(mockAuthenticator) .when(mockAuthenticator)
.authenticateReq(any[HttpRequest], any[List[String]], any[String], any[String]) .authenticateReq(any[HttpRequest], any[List[String]], any[String], any[String])
val result = underTest.apply(context, "").unsafeRunSync() val result = underTest.authenticate(context, "").unsafeRunSync()
result shouldBe Left(AuthRejected("Request signature could not be validated")) result shouldBe Left(AuthRejected("Request signature could not be validated"))
} }
} }

View File

@ -26,6 +26,7 @@ import org.mockito.Matchers._
import org.mockito.Mockito._ import org.mockito.Mockito._
import org.scalatest.mockito.MockitoSugar import org.scalatest.mockito.MockitoSugar
import org.scalatest.{BeforeAndAfterEach, Matchers, OneInstancePerTest, WordSpec} import org.scalatest.{BeforeAndAfterEach, Matchers, OneInstancePerTest, WordSpec}
import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.core.route.Monitor import vinyldns.core.route.Monitor
import scala.util.Failure import scala.util.Failure
@ -43,6 +44,9 @@ class VinylDNSDirectivesSpec
private val mockLatency = mock[Histogram] private val mockLatency = mock[Histogram]
private val mockErrors = mock[Meter] private val mockErrors = mock[Meter]
val vinylDNSAuthenticator: VinylDNSAuthenticator = new TestVinylDNSAuthenticator(
mock[AuthPrincipal])
class TestMonitor extends Monitor("test") { class TestMonitor extends Monitor("test") {
override val latency: Histogram = mockLatency override val latency: Histogram = mockLatency
override val errors: Meter = mockErrors override val errors: Meter = mockErrors

View File

@ -22,6 +22,7 @@ import akka.event.Logging._
import akka.http.scaladsl.model.headers.RawHeader import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import akka.http.scaladsl.server.directives.LogEntry import akka.http.scaladsl.server.directives.LogEntry
import vinyldns.core.domain.auth.AuthPrincipal
class VinylDNSServiceSpec class VinylDNSServiceSpec
extends WordSpec extends WordSpec
@ -30,6 +31,9 @@ class VinylDNSServiceSpec
with OneInstancePerTest with OneInstancePerTest
with VinylDNSDirectives { with VinylDNSDirectives {
val vinylDNSAuthenticator: VinylDNSAuthenticator = new TestVinylDNSAuthenticator(
mock[AuthPrincipal])
private def buildMockRequest( private def buildMockRequest(
path: String = "/path/to/resource", path: String = "/path/to/resource",
body: String = "request body") = { body: String = "request body") = {

View File

@ -16,12 +16,10 @@
package vinyldns.api.route package vinyldns.api.route
import akka.actor.ActorSystem
import akka.http.scaladsl.model.StatusCodes._ import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpRequest} import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpRequest}
import akka.http.scaladsl.server.{Directives, RequestContext, Route} import akka.http.scaladsl.server.{Directives, Route}
import akka.http.scaladsl.testkit.ScalatestRouteTest import akka.http.scaladsl.testkit.ScalatestRouteTest
import cats.effect._
import org.json4s.JsonDSL._ import org.json4s.JsonDSL._
import org.json4s._ import org.json4s._
import org.json4s.jackson.JsonMethods._ import org.json4s.jackson.JsonMethods._
@ -46,8 +44,6 @@ class ZoneRoutingSpec
with Matchers with Matchers
with GroupTestData { with GroupTestData {
def actorRefFactory: ActorSystem = system
private val okAuth = okGroupAuth private val okAuth = okGroupAuth
private val alreadyExists = Zone("already.exists.", "test@test.com") private val alreadyExists = Zone("already.exists.", "test@test.com")
private val notFound = Zone("not.found.", "test@test.com") private val notFound = Zone("not.found.", "test@test.com")
@ -329,10 +325,7 @@ class ZoneRoutingSpec
val zoneService: ZoneServiceAlgebra = TestZoneService val zoneService: ZoneServiceAlgebra = TestZoneService
override def vinyldnsAuthenticator( val vinylDNSAuthenticator = new TestVinylDNSAuthenticator(okAuth)
ctx: RequestContext,
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
IO.pure(Right(okAuth))
def zoneJson(name: String, email: String): String = def zoneJson(name: String, email: String): String =
zoneJson(Zone(name, email, connection = null, created = null, status = null, id = null)) zoneJson(Zone(name, email, connection = null, created = null, status = null, id = null))

View File

@ -28,6 +28,8 @@ vinyldns {
type = "vinyldns.core.crypto.NoOpCrypto" type = "vinyldns.core.crypto.NoOpCrypto"
} }
data-stores = ["mysql", "dynamodb"]
# default settings point to the setup from docker compose # default settings point to the setup from docker compose
mysql { mysql {
settings { settings {
@ -52,67 +54,50 @@ vinyldns {
} }
} }
# dynamodb settings, for local docker compose the secrets are not needed dynamodb {
dynamo { # dynamodb settings, for local docker compose the secrets are not needed
key = "x" settings {
secret = "x" key = "x"
endpoint = "http://vinyldns-dynamodb:8000" secret = "x"
} endpoint = "http://vinyldns-dynamodb:8000"
# dynamodb table settings follow
zoneChanges {
dynamo {
tableName = "zoneChange"
provisionedReads = 30
provisionedWrites = 30
} }
}
recordSet { repositories {
dynamo { record-set {
tableName = "recordSet" table-name = "recordSet"
provisionedReads = 30 provisioned-reads = 30
provisionedWrites = 30 provisioned-writes = 30
} }
} record-change {
table-name = "recordChange"
recordChange { provisioned-reads = 30
dynamo { provisioned-writes = 30
tableName = "recordChange" }
provisionedReads = 30 zone-change {
provisionedWrites = 30 table-name = "zoneChange"
} provisioned-reads = 30
} provisioned-writes = 30
}
users { user {
dynamo { table-name = "users"
tableName = "users" provisioned-reads = 30
provisionedReads = 30 provisioned-writes = 30
provisionedWrites = 30 }
} group {
} table-name = "groups"
provisioned-reads = 30
groups { provisioned-writes = 30
dynamo { }
tableName = "groups" group-change {
provisionedReads = 30 table-name = "groupChanges"
provisionedWrites = 30 provisioned-reads = 30
} provisioned-writes = 30
} }
membership {
groupChanges { table-name = "membership"
dynamo { provisioned-reads = 30
tableName = "groupChanges" provisioned-writes = 30
provisionedReads = 30 }
provisionedWrites = 30
}
}
membership {
dynamo {
tableName = "membership"
provisionedReads = 30
provisionedWrites = 30
} }
} }

View File

@ -20,27 +20,33 @@ import cats.data._
import cats.effect.IO import cats.effect.IO
import cats.implicits._ import cats.implicits._
import vinyldns.core.crypto.CryptoAlgebra import vinyldns.core.crypto.CryptoAlgebra
import org.slf4j.LoggerFactory
import vinyldns.core.repository.RepositoryName._ import vinyldns.core.repository.RepositoryName._
import scala.reflect.ClassTag import scala.reflect.ClassTag
object DataStoreLoader { object DataStoreLoader {
private val logger = LoggerFactory.getLogger("DataStoreLoader")
def loadAll[A <: DataAccessor]( def loadAll[A <: DataAccessor](
configs: List[DataStoreConfig], configs: List[DataStoreConfig],
crypto: CryptoAlgebra, crypto: CryptoAlgebra,
dataAccessorProvider: DataAccessorProvider[A]): IO[DataAccessor] = dataAccessorProvider: DataAccessorProvider[A]): IO[A] =
for { for {
activeConfigs <- IO.fromEither(getValidatedConfigs(configs, dataAccessorProvider.repoNames)) activeConfigs <- IO.fromEither(getValidatedConfigs(configs, dataAccessorProvider.repoNames))
dataStores <- activeConfigs.map(load(_, crypto)).parSequence dataStores <- activeConfigs.map(load(_, crypto)).parSequence
accessor <- IO.fromEither(generateAccessor(dataStores, dataAccessorProvider)) accessor <- IO.fromEither(generateAccessor(dataStores, dataAccessorProvider))
} yield accessor } yield accessor
def load(config: DataStoreConfig, crypto: CryptoAlgebra): IO[(DataStoreConfig, DataStore)] = def load(config: DataStoreConfig, crypto: CryptoAlgebra): IO[(DataStoreConfig, DataStore)] = {
logger.error(s"Attempting to load repos ${config.repositories.keys} from ${config.className}")
for { for {
className <- IO.pure(config.className) className <- IO.pure(config.className)
provider <- IO(Class.forName(className).newInstance.asInstanceOf[DataStoreProvider]) provider <- IO(Class.forName(className).newInstance.asInstanceOf[DataStoreProvider])
dataStore <- provider.load(config, crypto) dataStore <- provider.load(config, crypto)
} yield (config, dataStore) } yield (config, dataStore)
}
/* /*
* Validates that there's exactly one repo defined across all datastore configs. Returns only * Validates that there's exactly one repo defined across all datastore configs. Returns only

View File

@ -16,6 +16,7 @@
package vinyldns.dynamodb.repository package vinyldns.dynamodb.repository
import cats.effect.IO
import cats.implicits._ import cats.implicits._
import com.amazonaws.services.dynamodbv2.model.DeleteTableRequest import com.amazonaws.services.dynamodbv2.model.DeleteTableRequest
import com.typesafe.config.{Config, ConfigFactory} import com.typesafe.config.{Config, ConfigFactory}
@ -77,10 +78,10 @@ class DynamoDBDataStoreProviderIntegrationSpec extends DynamoDBIntegrationSpec {
) )
val userRepo = dataStore.get[UserRepository](user) val userRepo = dataStore.get[UserRepository](user)
val save = userRepo.map(_.save(testUser)).parSequence val save = userRepo.map(_.save(testUser)).sequence[IO, User]
save.unsafeRunSync() shouldBe Some(testUser) save.unsafeRunSync() shouldBe Some(testUser)
val get = userRepo.map(_.getUser(testUser.id)).parSequence val get = userRepo.map(_.getUser(testUser.id)).sequence[IO, Option[User]]
get.unsafeRunSync().flatten shouldBe Some(testUser) get.unsafeRunSync().flatten shouldBe Some(testUser)
} }
} }

View File

@ -74,10 +74,17 @@ class DynamoDBDataStoreProvider extends DataStoreProvider {
def initializeSingleRepo[T <: Repository]( def initializeSingleRepo[T <: Repository](
repoName: RepositoryName, repoName: RepositoryName,
fn: DynamoDBRepositorySettings => IO[T]): IO[Option[T]] = { fn: DynamoDBRepositorySettings => IO[T]): IO[Option[T]] =
logger.info(s"Loading dynamodb repo for type: $repoName") repoSettings
repoSettings.get(repoName).map(fn(_)).parSequence .get(repoName)
} .map { configuredOn =>
for {
_ <- IO(logger.error(s"Loading dynamodb repo for type: $repoName"))
repo <- fn(configuredOn)
_ <- IO(logger.error(s"Completed dynamodb load for type: $repoName"))
} yield repo
}
.sequence
( (
initializeSingleRepo[UserRepository]( initializeSingleRepo[UserRepository](

View File

@ -1 +1 @@
sbt.version=1.1.4 sbt.version=1.1.6